前面已经讲述了Mysql Innodb存储引擎下的事务隔离级别与锁机制,我们知道了
Mysql的四种隔离级别,其中最常用的是可重复读(Repeatable Read) 和 读已提交(Read Committed) . RR相比较RC,解决了不可重复读问题,而RC每次都能读到数据行最新的数据. 他们的底层则是通过MVCC机制实现的.
MVCC机制
MVCC即多版本的并发控制(Multi-Version Concurrency Control).它主要通过undo日志版本链和 ReadView一致性试图实现.
undo日志版本链
// 插入数据
insert into student(id, name, age) VALUES (1,'鸣人',16);
// 则生成如下undo日志版本
undo日志版本链包含 trx_id(事务id)和roll_point(指针)
// 后续有其他事务对该记录进行更新
// 事务200
update student set name = '佐助' where id = 1;
// 事务300
update student set name = '我爱罗' where id = 1;
// 事务400
update student set name = '宁次' where id = 1;
// 每次操作同一行数据都会生成对应的一个undo日志,通过roll_pointer把这些日志记录连接起来,形成一个历史记录版本链。如下图:
黄色代表历史undo日志,蓝色代表最新的undo日志
// undo日志版本链的删除
1.当是insert操作的时候,当事务提交则可以删除undo日志版本链了.
2.当是update操作的时候,当事务提交后不会立刻删除undo日志版本链,需要确定该行数据已经没有被其他事务关联了,才会删除.
ReadView
一致性视图是一个时间点的快照,用于决定事务在读取数据时能够看到哪些数据版本.
// 视图组成
m_ids:生成ReadView时,当前系统的活跃事务id集合
min_trx_id:生成ReadView时,活跃事务id最小值(m_ids的最小值)
max_trx_id:生成ReadView时,系统分配的下一个事务id值
creator_trx_id:生成ReadView时,当前事务的id
// 举例以上图undo日志版本链为例,按下图时间轴执行顺序
生成的ReadView为下图
版本链对比规则
// 对比规则
1.如果访问版本的trx_id值与ReadView的creator_trx_id值相同,则表示访问自己事务中修改的记录,所以可以访问。
2.如果访问版本的trx_id值小于min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以可以访问.
3.如果访问版本的trx_id值大于或等于max_trx_id值,表明生成该版本的事务在当前事务生成ReadView前还没有提交,所以不可以访问.
4.如果访问版本的trx_id值在min_trx_id与max_trx_id值之间,则需要判断trx_id值是否在m_ids中,如果在说明生成ReadView时是活跃事务,所以不可以访问.
如果不在说明生成ReadView时不是活跃事务(表明已提交),所以可以访问.
实践说明
根据下图时间轴操作,我们主要关注:
- trx_id = 400 在时间轴4,8返回的值
- trx_id=500在时间轴7,9返回的值
日志版本链
根据上图id=1的记录的undo日志版本链为下图:
ReadView
ReadView是在第一次执行select查询语句的时候生成的
// 以trx_id = 400为视角,生成的ReadView为
m_ids:[200,300,400,500]
min_trx_id:200
max_trx_id:501
creator_trx_id:400
// 以trx_id = 500为视角,生成的ReadView为
m_ids:[300,400,500]
min_trx_id:200
max_trx_id:501
creator_trx_id:500
查找数据
// 以trx_id = 400为例
// 当处于时间轴4的时候
此时undo版本链只有初始第一次数据,所以从这条数据开始查找,该条数据的trx_id =100,根据比较规则,小于当前min_trx_id(即100<200),所以该条数据是可见的,所以数据肯定是name='鸣人';
// 当处于时间轴8时
此时undo版本链已经有上图全部数据行里,所以从最新数据开始查找
1.从trx_id = 300开始比较,300在m_ids集合中,视图生成时是活跃事务,所以不可见;
2.继续往上看trx_id=200,也在m_ids中,所以不可见;
3.最终来到trx_id=100,可见;所以最终结果还是name='鸣人';
// 以trx_id = 500为例
// 当处于时间轴7的时候
此时undo版本链已经有上图全部数据行里,所以从最新数据开始查找
1.从trx_id = 300开始比较,300在m_ids集合中,视图生成时是活跃事务,所以不可见;
2. 继续往上看trx_id=200,不在m_ids中,所以可见;最终结果name='我爱罗'
// 当处于时间轴9的时候
如果是RR级别,则ReadView不变,所以结果还是name='我爱罗';
如果是RC级别,每次都会重新生成ReadView
m_ids:[500]
min_trx_id:500
max_trx_id:501
creator_trx_id:500
根据规则可以知道:最终结果为name='宁次';