####### mysql.innoDB的乐观锁mvcc机制 #######

转自:https://juejin.cn/post/6844904096378404872,仅做个人备份,浏览请务必看原文

转自:https://juejin.cn/post/6844904096378404872,仅做个人备份,浏览请务必看原文

转自:https://juejin.cn/post/6844904096378404872,仅做个人备份,浏览请务必看原文

为了方便描述,首先我们创建一个表book,就三个字段,分别是主键book_id, 名称book_name, 库存stock。然后向表中插入一些数据:

INSERT INTO book VALUES(1, '数据结构', 100);
INSERT INTO book VALUES(2, 'C++指南', 100);
INSERT INTO book VALUES(3, '精通Java', 100);
复制代码

版本链

对于使用InnoDB存储引擎的表,其聚簇索引记录中包含了两个重要的隐藏列:

  • 事务ID(DB_TRX_ID):每当事务对聚簇索引中的记录进行修改时,都会把当前事务的事务id记录到DB_TRX_ID中。
  • 回滚指针(DB_ROLL_PTR):每当事务对聚簇索引中的记录进行修改时,都会把该记录的旧版本记录到undo日志中,通过DB_ROLL_PTR这个指针可以用来获取该记录旧版本的信息。

如果在一个事务中多次对记录进行修改,则每次修改都会生成undo日志,并且这些undo日志通过DB_ROLL_PTR指针串联成一个版本链,版本链的头结点是该记录最新的值,尾结点是事务开始时的初始值。

例如,我们在表book中做以下修改:

BEGIN;

UPDATE book SET stock = 200 WHERE id = 1;

UPDATE book SET stock = 300 WHERE id = 1;
复制代码

那么id=1的记录此时的版本链就如下图所示:

 

ReadView

对于使用Read Uncommitted隔离级别的事务来说,只需要读取版本链上最新版本的记录即可;对于使用Serializable隔离级别的事务来说,InnoDB使用加锁的方式来访问记录。而Read CommittedRepeatable Read隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在Read CommittedRepeatable Read隔离级别下,版本链中哪个版本是能被当前事务读取的。于是ReadView的概念被提出以解决这个问题。

ReadView相当于某个时刻表记录的一个快照,在这个快照中我们能获取到与当前记录相关的事务中,哪些事务是已提交的稳定事务,哪些是正在活跃的事务,哪些是生成快照之后才开启的事务。由此我们就能根据可见性比较算法判断出版本链中能被读取的最新版本记录。

可见性比较算法是基于事务ID的比较算法。首先我们需要知道的一个事实是:事务id是递增分配的。从ReadView中我们能获取到生成快照时刻系统中活跃的事务中最小和最大的事务id(最大的事务id实际上是系统中将要分配给下一个事务的id值),这样我们就得到了一个活跃事务id的范围,我们可称之为ACTIVE_TRX_ID_RANGE。那么小于这个范围的事务id对应的事务都是已提交的稳定事务,大于这个范围的事务都是在快照生成之后才开启的事务,而在ACTIVE_TRX_ID_RANGE范围内的事务中除了正在活跃的事务,也都是已提交的稳定事务。

有了以上信息之后,我们顺着版本链从头结点开始查找最新的可被读取的版本记录:

1、首先判断版本记录的DB_TRX_ID字段与生成ReadView的事务对应的事务ID是否相等。如果相等,那就说明该版本的记录是在当前事务中生成的,自然也就能够被当前事务读取;否则进行第2步。

2、如果版本记录的DB_TRX_ID字段小于范围ACTIVE_TRX_ID_RANGE,表明该版本记录是已提交事务修改的记录,即对当前事务可见;否则进行下一步。

3、如果版本记录的DB_TRX_ID字段位于范围ACTIVE_TRX_ID_RANGE内,如果该事务ID对应的不是活跃事务,表明该版本记录是已提交事务修改的记录,即对当前事务可见;如果该事务ID对应的是活跃事务,那么对当前事务不可见,则读取版本链中下一个版本记录,重复以上步骤,直到找到对当前事务可见的版本。

如果某个版本记录经过以上步骤判断确定其对当前事务可见,则查询结果返回此版本记录;否则读取下一个版本记录继续按照上述步骤进行判断,直到版本链的尾结点。如果遍历完版本链没有找到对当前事务可见的版本,则查询结果为空。

MySQL中,Read CommittedRepeatable Read隔离级别下的区别就是它们生成ReadView的时机不同。


作者:hudingyu
链接:https://juejin.cn/post/6844904096378404872
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

猜你喜欢

转载自blog.csdn.net/chushoufengli/article/details/114594717