Mysql-事务(隔离级别,事务底层原理,MVCC)

什么是事务?有哪些特性?

事务 :事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败。
事务特性:

  • 原子性(Atomicity): 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency):事务前后数据的完整性必须保持一致
  • 隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。隔离性由隔离级别保障。
  • 持久性(Durability): 一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

事务并发可能产生那些问题?

1 脏读:一个事务读到了另一个事务未提交的数据

2 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发事务中的多次查询结果不一致,数据中的值不一致。

3 虚读 /幻读:一个事务读到了另一个事务已经插入(insert)的数据。导致事务中多次查询的结果不一致,比如一个事务向表中插入了一条数据,这个时候另一个事务读取到了这条数据这就是幻读,实际上不应该读到这条数据。

4 丢失更新,举个例子比如事务T1,T2都读取了表中的某一行数据,事务T1对一个表的数据做了更新更为值A,事务T1提交以后,T2也对这个数据进行了修改改为B并提交,这个时候在事务T1里面查询这个数据得到的值是B,T1对数据的修改A被丢失了。
导致这个问题的根本原因就是并发问题,这两个事务可以同时对这个数据进行修改

事务的隔离级别

1 read uncommitted 读未提交【RU】,一个事务读到另一个事务没有提交的数据

存在:3个问题(脏读、不可重复读、幻读)。

2 read committed 读已提交【RC】,一个事务读到另一个事务已经提交的数据

存在:2个问题(不可重复读、幻读)。

解决:1个问题(脏读)

3 repeatable read:可重复读【RR】,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交

解决:3个问题(脏读、不可重复读、幻读)msql默认的隔离级别

4 serializable 串行化,同时只能执行一个事务,相当于事务中的单线程

解决:3个问题(脏读、不可重复读、幻读)

事务的底层原理

前面我们提到了四个问题,我们来解决这些问题。

解决方案一:基于锁并发控制LBCC

1 解决丢失更新的问题

我们在事务T1读取这样行数据的时候就直接加读锁,这个时候T2想要来修改这个数据需要加写锁是不被允许的,就必须等待这个读锁释放,T2才能对对数据进行更新,锁释放的时候事务T1已经执行完成,保证了不会丢失事务T1的更新。

2 解决读已提交(Read Committed)

读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。这可以通过"瞬间共享读锁"和"排他写锁"实现, 即事务需要对某些数据进行修改必须对这些数据加 X 锁,事务结束后才会释放X锁,读数据时需要加上 S 锁,当数据读取完成后立刻释放 S 锁,不用等到事务结束。

3 解决可重复读取(Repeatable Read)

禁止不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

Mysql默认使用该隔离级别。这可以通过"共享读锁"和"排他写锁"实现,即事务需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成并不立刻释放 S 锁,而是等到事务结束后再释放。

解决方案二:基于版本并发控制MVCC

MVCC全称叫多版本并发控制,是RDBMS常用的一种并发控制方法,用来对数据库数据进行并发访问,实现事务。核心思想是读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突非常重要,极大的增加了系统的并发性能,这也是为什么几乎所有的RDBMS,都支持MVCC的原因。MVCC 实现原理关键在于数据快照,不同的事务访问不同版本的数据快照,从而实现事务下对数据的隔离级别。虽然说具有多个版本的数据快照,但这并不意味着必须拷贝数据,保存多份数据文件(这样会浪费存储空间),InnoDB通过事务的Undo日志巧妙地实现了多版本的数据快照。
这里介绍一下UndoLog

InnoDB 引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到 undo log 里,比如:

1)在插入一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录删掉

2)在删除一条记录时,要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中.

3)在更新一条记录时,要把被更新的列的旧值记下来,这样之后回滚时再把这些列更新为旧值

会产生两种日志:

1 insert Undo 日志:在Insert操作中产生的Undo日志

由于insert操作的记录只对本身可见,对于其它事务此记录是不可见的,所以insert undo log 可以在事务提交后直接删除,不需要等待回收

2 Update Undo日志:Update或Delete 操作中产生的Undo日志

Update操作会对已经存在的行记录产生影响,为了实现MVCC多版本并发控制机制,因此Update Undo日志不能在事务提交时就删除,而是在事务提交时将日志放入指定区域,等待 Purge 线程进行最后的删除操作。

这个时候比如我们需要对第二次更新做回滚操作,这个时候只需要顺着RollPointer指针回退一个版本。

有了上面的Undo Log 我们可以结合是ReadView来实现版本控制
ReadView

ReadView是张存储事务id的表,主要包含当前系统中有哪些活跃的读写事务,把它们的事务id放到一个

列表中。结合Undo日志的默认字段【事务trx_id】来控制那个版本的Undo日志可被其他事务看见。

m_ids:表示在生成ReadView时,当前系统中活跃的读写事务id列表

m_low_limit_id:事务id下限,表示当前系统中活跃的读写事务中最小的事务id,m_ids事务列表中的最小事务id

m_up_limit_id:事务id上限,表示生成ReadView时,系统中应该分配给下一个事务的id值

m_creator_trx_id:表示生成该ReadView的事务的事务id

  • ReadView怎么产生,什么时候生成?

    开启事务之后,在第一次查询(select)时,生成ReadView

    RC 和 RR 隔离级别的差异本质是因为MVCC中ReadView的生成时机不同

    MVCC 怎么保证事务的RC和RR隔离级别可以看这篇文章, 根据下面判断可见性的原理做了一个案例演示。
    MVCC + ReadView案例

  • 如何判断可见性?

    开启事务执行第一次查询时,首先生成ReadView,然后依据Undo日志和ReadView按照判断可见性,

    按照下边步骤判断记录的版本链的某个版本是否可见。

如果被访问版本的 trx_id 属性值,小于ReadView中的事务下限id,表明生成该版本的事务在生

成 ReadView 前已经提交,所以该版本可以被当前事务访问。

如果被访问版本的 trx_id 属性值,等于ReadView中的 m_creator_trx_id ,可以被访问。

如果被访问版本的 trx_id 属性值,大于等于ReadView中的事务上限id,在生成 ReadView 后才产生的数据,所以该版本不可以被当前事务访问。

如果被访问版本的 trx_id 属性值,在事务下限id和事务上限id之间,那就需要判断是不是在

m_ids 列表中。如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

循环判断Undo log中的版本链某一的版本是否对当前事务可见,如果循环到最后一个版本也不可见的

话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。

MVCC下的读操作

在MVCC并发控制中,读操作可以分成两类:快照读 (Snapshot Read)与当前读 (Current Read)

快照读:读取的是记录的可见版本 (有可能是历史版本),不用加锁。刚才案例中都是快照读。

当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录

  • 快照读
    简单的select操作,属于快照读,不加锁。
  • 当前读
    特殊的读操作,或者插入/更新/删除操作,属于当前读,需要加锁,需要读取最新的数据。
sql 复制代码
select * from table where ? lock in share mode; # 加读锁
select * from table where ? for update;# 加写锁
insert into table values (...);# 加写锁
update table set ? where ?;# 加写锁
delete from table where ?;# 加写锁
相关推荐
独行soc42 分钟前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
White_Mountain1 小时前
在Ubuntu中配置mysql,并允许外部访问数据库
数据库·mysql·ubuntu
Code apprenticeship1 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站1 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
老王笔记1 小时前
GTID下复制问题和解决
mysql
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
Lojarro3 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
梦想平凡3 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
TianyaOAO3 小时前
mysql的事务控制和数据库的备份和恢复
数据库·mysql
Ewen Seong3 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存