多版本并发控制原理分析之数据可见性算法

MySQL InnoDB通过多版本并发控制,对每一行记录都写入3个隐藏字段(TRX_ID、ROLL_POINT、ROW_ID),使可重复读隔离级别下达到事务开启且创建快照后其他任何其他事务的更新均对其不可见,至于其内部具体如何实现的,则通过下面的文章进行记录。

四种事务隔离界别及解决的问题

首先需要回顾下MySQL的四种事务隔离级别,以及其为了解决的问题。

级别 symbol 描述
读未提交 READ-UNCOMMITTED 存在脏读、不可重复读、幻读的问题
读已提交 READ-COMMITTED 解决脏读的问题,存在不可重复读、幻读的问题
可重复读 REPEATABLE-READ mysql 默认级别,解决脏读、不可重复读的问题,存在幻读的问题。使用 MMVC机制 实现可重复读
序列化 SERIALIZABLE 解决脏读、不可重复读、幻读,可保证事务安全,但完全串行执行,性能最低

MySQL的可重复读解决了脏读、不可重复读问题,但通过MVCC并未解决幻读问题,幻读是结合next-key-lock(间隙锁)来实现的。关于此处内容文章mysql 幻读的详解、实例及解决办法讲得不错。

概念

TrxId = 当前记录(行数据)最后被修改的事务id
UpId = 当前活跃最小事务id
LowLimitId = 下一个即将分配的事务id(当前最大事务Id+1)
活跃事务列表 = 当前所有开启了事务但未提交的事务。

  1. 如果当前记录最后被修改的事务id 小于 最小活跃事务id(当前所有活跃会话开启前事务提交的数据) 或者 当前记录最后被修改的事务id 和 当前事务id 相等(自己在当前事务下更新过该记录的数据), 则可见
  2. 当前记录最后被修改的事务id 大于等于 下一个即将被分配的事务id(low-limit-id是开启快照读时生成,当low-limit-id小于TRX_ID则说明此数据在当前快照创建后已修改,此数据不可见需要继续通过RollId往上找),则不可见,通过回滚指针继续向上找。
  3. 当前记录最后被修改的事务id 在 活跃事务列表(活跃事务列表是开启快照读时生成,则说明是该事务创建时已经在活跃的事务) 中,则不可见,通过回滚指针继续向上找。
  4. 其他所有情况,都可见
可重复读隔离级别下算法

前提条件

基于MySQL 的 可重复读 隔离级别下。

快照读只有在当前事务第一次执行读取操作(SELECT)后才会生成。
此时已经固定下来包括:

  1. 活跃事务id列表
  2. 最小活跃最小事务id
  3. 下一个即将分配的事务id

但没有固定 当前记录最后被修改的事务id,此字段根据每次查询时当前记录最后被更新的事务来更新(永远读最新的)。

场景分析

场景一:创建快照前的更新

同时开启4个事务,但事务2第一次快照读在事务4提交之后,故事务4的数据修改对于事务2可见。

结合流程图分析:

当前记录最后被修改事务id(TRX_ID)=4, upid=1,low-limit-id=5,当前事务id=2,
活跃事务列表=(1、2、3),不包含4,因为4已提交

  1. TRX_ID < upId = false,TRX_ID = 当前事务id = false
  2. TRX_ID >= low-limit-id = false
  3. TRX_ID in 活跃事务列表 = false
  4. 上述条件均不支持,数据可见。
可重复读隔离级别-场景1

场景二:创建快照后开启事务的更新

开始只开启事务1和2、当事务2执行select之后(生成了快照读),再开启了事务3、4,事务4执行了update语句且进行了提交,事务4的更新对于事务2第二次select来说处于高水位,是不可见的。

结合流程图分析:

当前记录最后被修改事务id(TRX_ID)=4, upid=1,low-limit-id=3,当前事务id=2,
活跃事务列表=(1、2),不包含3、4,因为2的快照读是在第一个select时创建的,那是3、4还未开启事务。

  1. TRX_ID < upId = false,TRX_ID = 当前事务id = false
  2. TRX_ID >= low-limit-id = true,符合,即不可见(其实就是高水位,是在当前快照之后创建的事务,当然不可见)
可重复读隔离级别-场景2

场景三:创建快照同时开启事务的更新

同时开启4个事务,事务2初次执行select时已创建快照读,在后续事务4执行了update操作进行了提交,事务4的更新对于事务2第二次select来说处于高水位,是不可见的。

结合流程图分析:

当前记录最后被修改事务id(TRX_ID)=4, upid=1,low-limit-id=5,当前事务id=2,
活跃事务列表=(1、2、3、4)。

  1. TRX_ID < upId = false, TRX_ID = 当前事务id = false
  2. TRX_ID >= low-limit-id = false
  3. TRX_ID in 活跃事务列表 = true,即不可见,保证了事务2在两次select时的查询均是一致的,事务4的更新对于事务2来说永远不可见。
可重复读隔离级别-场景3

记录

本文基于慕课网课程探秘 MySQL 多版本并发控制原理