事务ACID回顾

InnDB引擎下,具备事务功能,事务具备ACID(原子性、一致性、隔离性、持久性),一致性其实是目的,由原子性、隔离性和持久性共同来保证!原子性是由undo log来进行保证的(回滚的时候采用undo log),持久性由InnoDB的redo log、undo log、 binlog来保证,而隔离性指的是它有四个隔离级别,分别是:

  • 读未提交
  • 读提交
  • 可重复读
  • 串行化

mysql 设置为读已提交 mysql读已提交 原理_java

其中我们用的比较多的是 读提交(RC) 和 可重复读(RR),下面来详细介绍一下他们是如何通过MVCC多版本并发控制实现的。

MVCC

在MySQL InnoDB存储引擎下,RC、RR基于MVCC(多版本并发控制)进行并发事务控制

MVCC是基于”数据版本”对并发事务进行访问

下面举一个例子来说明实现原理:

mysql 设置为读已提交 mysql读已提交 原理_mysql_02

现在有三个事务,事务id分别是 trx_id = 1、2、3、4;前面三个事务都对张三这个人做了name的更新并且提交,事务4就是在两个时间段去做了“读”操作,我们先来看在“读提交”的隔离级别下,事务4的两次读操作会读出什么结果呢?

如果是RC级别,那么 select1 = 张三,select2=张小三,在图中可以看出这个时序关系。

如果是RR级别,那么select1= 张三,select2=张三,两次结果相同。

下面来分析底层实现原理:

undo log版本链

先来了解一下undo log版本链,他用链的形式存储了数据的变化:

mysql 设置为读已提交 mysql读已提交 原理_mysql_03

其中trx_id代表导致当前数据版本的事务id,DB_ROLL_PTR储存着上一个数据版本的数据地址

这里补充一个undo log回滚的过程:

如果要回滚,那么就得

比如插入一条记录,得把这个记录的id记录下来,回滚的时候直接删掉这个id即可。

删除记录则要把记录的内容保留,回滚的时候插入即可。修改的时候则把旧值记录,回滚时直接把旧值写入。

问:

undo log不是会被删除吗?中间数据万一被删了版本链不就断了吗?

undo log版本链不是立即删除,mysql确保版本链数据不再被“引用”后再进行删除!

ReadView

ReadView就是读视图,

ReadView是“快照读”SQL执行时MVCC提取数据的依据.

快照读就是最普通的Select查询SQL语句

当前读指代执行下列语句时进行数据读取的方式(比如插入或者删除数据,必须是最新的表的状态下去做变更操作!)

Insert、Update、Delete、

Select…for update

Select…lock in share mode

只有当快照读的时候才会用到MVCC

ReadView的数据结构:

ReadView是一个数据结构,包含4个字段

  • m_ids:当前活跃的事务编号集合
  • min_trx_id:最小活跃事务编号
  • max_trx_id:预分配事务编号,当前最大事务编号+1
  • creator_trx_id:ReadView创建者的事务编号

读已提交(RC)下

读已提交(RC):在每执行一次快照读的时候,都去生成一个ReadView,所以两次生成了两个不同的ReadView读视图

mysql 设置为读已提交 mysql读已提交 原理_mysql 设置为读已提交_04


mysql 设置为读已提交 mysql读已提交 原理_mysql 设置为读已提交_05

分析:

在事务4的第一个select语句执行时,生成了一个ReadVIew,它是用来判断读取undo log版本链中具体哪一个数据版本的,根据右侧的规则一步步进行判断即可,

举例:

先对TRX_ID = 3进行判断:

从undo log的最新一个版本(TRX-ID = 3)开始遍历,当前的事务id为3,不等于creator_id,也就是说,select的事务和这个数据版本不是同一个事务,那就继续向下判断(如果相等,说明select事务之前可能做了一个更新操作,之后才select 是同一个事务中的前后操作 所以肯定是可以读的!)

继续判断:当前的事务id和min_trx_id相比,比min要大,所以还得继续判断(如果确实比min小,那么说明是在最小活跃事务之前数据提交得,是可以访问的)

继续判断:当前事务id和max_trx_id相比,比max要小,所以还得继续判断(如果比max大,说明这个数据版本在的事务是在ReadView生成之后才开启的,不允许访问)

继续判断:如果当前事务在min和max之前,就是最小活跃事务和最大活跃事务之间,并且还要判断当前事务存在于活跃事务中(m_ids:记录了还有哪些事务没有被提交),如果不在活跃事务中也就是提交了,那就可以访问

先对TRX_ID = 2进行判断:

先对TRX_ID = 1进行判断:

满足条件 trx_id < min_trx_id(2) 成立,说明在最小活跃事务之前就提交了已经,可以访问!

可重复读(RR)下

仅在第一次执行快照读时生成ReadView,后续快照读复用前面的ReadVIew

mysql 设置为读已提交 mysql读已提交 原理_mysql 设置为读已提交_06

RR级别下使用MVCC能避免幻读吗?

能,但不完全能!

  1. 连续多次快照读,ReadView会产生复用,没有幻读问题
  2. 当两次快照读之间存在当前读,ReadView会重新生成,导致产生幻读

举例:

在事务B中两次select快照读的中间有一个 更新语句(当前读),这种情况下,第二次的快照读生成的ReadVIew就会重新生成,而不是复用,否则在同一个事务中前后就会造成不一致!