MVCC
undo log版本链
MySQL中的每条数据其实都有两个隐藏字段。
trx_id
:最近一次更新这条数据的事务ID。roll_pointer
:指向更新事务发生之前生成的undo log
。

此时事务B
也修改了数据,那么更新之前会生成一个undo log
,并让新数据的roll_pointer
指向这个undo log
的回滚日志。

假设事务C
也要修改数据,同样的道理。

因此,从事务A
到事务C
,就形成了通过roll_pointer
指针串起来的undo log
版本链。
ReadView机制
执行事务的时候,会生成一个叫做ReadView
的东西,它有四个关键的字段。
m_ids
:正在执行但还未提交的活跃事务ID集合。min_trx_id
:m_ids
中最小的事务ID值。max_trx_id
:下一个要生成的事务ID值,也是m_ids
中最大的事务ID值,值一般是最大trx_id + 1
。creator_trx_id
:创建ReadView
的事务ID值。
假设数据库中有一行原始数据,事务ID=1。

此时过来两个请求,分别代表事务A
(ID=2)和事务B
(ID=3),事务A
执行读请求,事务B
执行写请求。

现在事务A开启一个ReadView。

整个流程为:

事务A
第一次查询数据时,会先判断当前这行数据的trx_id
是否小于min_trx_id
,如果小于,就可以正常查询获得数据。事务B
接着把这条数据改为了数据B,然后把trx_id
设为自己的trx_id
,同时生成一个undo log
链指向之前的数据,事务B
提交。事务A
此时再次查询,如果min_trx_id < trx_id < max_trx_id
,说明这条数据的事务可能是跟自己差不多同时开启的,为了验证,继续去查,看trx_id
是否在m_ids
中。如果
trx_id ∈ m_ids
,说明这行数据是不能查询的,会顺着这条数据的roll_pointer
去查undo log
版本链。当找到最近一条数据的
trx_id < min_trx_id
时,说明该事务必然在事务A
开启前就已提交,可以放心地查询数据了。

接着
事务A
更新了这行数据,trx_id
修改为2,同时生成undo log
。事务A
继续发送读请求,发现trx_id = 2
,就是自己的值,可以直接查询。事务A
执行时,突然插入了事务C
,更新事务A查询的那行记录并提交,导致trx_id = 4
且大于max_trx_id
,说明事务A
已经不能再查询到最新数据了。事务A
顺着undo log
往前翻,找到trx_id <= min_trx_id
或者trx_id == creator_trx_id
的记录,读取这个版本的数据值。
通过undo log多版本链
+ 事务启动时开启的ReadView
,就可以准确地知道应该读取哪个版本的数据。并且保证多个事务混合在一起时相互之间不会干扰,也就实现了多事务并发执行时的数据隔离特性。
ReadView
实际上是一套基于undo log
的视图机制:事务自己更新的数据,或者是在它之前更新的数据,都是可读的,但如果生成ReadView
后有别的事务修改了数据,当前事务是读不到的。
Read Committed隔离级别
Read Committed
隔离级别可以消除脏读,但是无法消除可重复读
和幻读
问题。
基于ReadView
实现RC
隔离级别的核心要点在于:每次事务发起查询,都会重新生成一个ReadView
视图,数据库中有一行数据,是之前的事务更新进去的,但当前有两个活跃事务,事务A
(trx_id=2)和事务B
(trx_id=3)。

事务B
发起写请求,更新了数据,同时会生成一个undo log
。

此时,事务A
发起读请求,同时生成一个ReadView
。

事务A
查询时,发现当前数据的trx_id = 3
,在m_ids
中(因事务B
还未提交,属于活跃事务),但依据ReadView
机制,事务A
是无法读取到事务B
修改的数据的。
事务A
顺着undo log
往前翻,找到某个版本的trx_id = 1
,很明显trx_id < min_trx_id
,事务A
就可以读取到这个版本的数据了。

接着,事务B
提交了——紧接着,事务A
再次发起读请求,并重新生成一个ReadView
,此时ReadView
的m_ids
中已经没有事务B
的ID了。
当事务A
查询时,发现数据的trx_id = 3 < max_trx_id
且不在m_ids
中,因此可以读取数据。

实际上,基于undo log多版本链
+ ReadView机制
实现的多事务并发执行的RC
、RR
隔离级别,就是数据库的MVCC多版本并发控制机制
在起作用。
其本质是当多个事务并发地运行且读写同一批数据的时候,应该如何协调相互之间的数据可见性。
Repeatable Read隔离级别
Repeatable Read(简称RR)
隔离级别是MySQL默认的隔离级别,还可以解决标准SQL
隔离级别无法解决的幻读
问题。
在RR
隔离级别下,同一条数据,事务无论读多少次,都是同一个值——哪怕别的事务提交了也是如此。
假设数据初始状态。

事务A
发起读请求,生成ReadView
。

接着事务B
更新了这条数据。

因为没有重新生成ReadView
,因此m_ids
的值保持不变,依据ReadView
规则,如果此时事务A
再次查询,是查询不到修改后的数据的,只能顺着undo log
往前翻。
基于严格的ReadView
规则,就此解决脏读
、不可重复读
和幻读
问题。

总结
感谢支持
更多内容,请移步《超级个体》。