脚本宝典收集整理的这篇文章主要介绍了MVCC详解,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
它读取的数据库记录,都是当前最新
的版本
,会对当前读取的数据进行加锁
,防止其他事务修改数据。是悲观锁
的一种操作。
如下操作都是当前读:
快照读的实现是基于多版本
并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本
的数据。
如下操作是快照读:
它的实现原理主要是隐藏字段
,undo log
,Read View
来实现的
在内部,InnoDB
存储引擎为每行数据添加了三个 隐藏字段open in new window:
DB_TRX_ID(6字节)
:表示最后一次插入或更新该行的事务 id。此外,delete
操作在内部被视为更新,只不过会在记录头 Record header
中的 deleted_flag
字段将其标记为已删除DB_ROLL_PTR(7字节)
回滚指针,指向这条记录
的上一个版本
DB_ROW_ID(6字节)
:如果没有设置主键且该表没有唯一非空索引时,InnoDB
会使用该 id 来生成聚簇索引undo log
主要有两个作用:
MVCC
,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log
读取之前的版本数据,以此实现非锁定读对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer
属性连接成一个链表
,我们把这个链表称之为版本链
,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。
undo log
主要分为两种:
insert undo log
代表事务在insert新记录时产生的undo log , 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log(主要)
事务在进行update或delete时产生的undo log ; 不仅在事务回滚时需要,在快照读时也需要;
所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
主要有以下字段:
low_limit_id
:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见up_limit_id
:活跃事务列表 trx_ids
中最小的事务 ID,如果 m_ids
为空,则 up_limit_id
为 low_limit_id
。小于这个 ID 的数据版本均可见trx_ids
(活跃事务列表):Read View
创建时其他未提交的活跃事务 ID 列表。创建 Read View
时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids
不包括当前事务自己和已提交的事务(正在内存中)creator_trx_id
:创建该 Read View
的事务 IDdb_trx_id
< up_limit_id
|| db_trx_id
== creator_trx_id
(显示)
如果数据事务ID小于read view中的最小活跃事务ID
,则可以肯定该数据是在当前事务启之前
就已经存在
了的,所以可以显示
。
或者数据的事务ID
等于creator_trx_id
,那么说明这个数据就是当前事务自己生成的
,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示
的。
db_trx_id
>= low_limit_id
(不显示)
如果数据事务ID大于read view 中的当前系统的最大事务ID
,则说明该数据是在当前read view 创建之后才产生
的,所以数据不显示
。如果小于则进入下一个判断
db_trx_id
是否在活跃事务
(trx_ids)中
不存在
:则说明read view产生的时候事务已经commit
了,这种情况数据则可以显示
。已存在
:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。虽然 RC 和 RR 都通过 MVCC
来读取快照数据,但由于 生成 Read View 时机不同,从而在 RR 级别下实现可重复读
举个例子:
假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为
:
由于 RC 级别下每次查询都会生成Read View
,并且事务 101、102 并未提交,此时 103
事务生成的 Read View
中活跃的事务 m_ids
为:[101,102] ,m_low_limit_id
为:104,m_up_limit_id
为:101,m_creator_trx_id
为:103
DB_TRX_ID
为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 m_ids
列表中查找,发现 DB_TRX_ID
存在列表中,那么这个记录不可见DB_ROLL_PTR
找到 undo log
中的上一版本记录,上一条记录的 DB_TRX_ID
还是 101,不可见DB_TRX_ID
为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 name = 菜花
时间线来到 T6 ,数据的版本链为
:
因为在 RC 级别下,重新生成 Read View
,这时事务 101 已经提交,102 并未提交,所以此时 Read View
中活跃的事务 m_ids
:[102] ,m_low_limit_id
为:104,m_up_limit_id
为:102,m_creator_trx_id
为:103
DB_TRX_ID
为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 m_ids
列表中查找,发现 DB_TRX_ID
存在列表中,那么这个记录不可见DB_ROLL_PTR
找到 undo log
中的上一版本记录,上一条记录的 DB_TRX_ID
为 101,满足 101 < m_up_limit_id,记录可见,所以在 T6
时间点查询到数据为 name = 李四
,与时间 T4 查询到的结果不一致,不可重复读!时间线来到 T9 ,数据的版本链为
:重新生成 Read View
, 这时事务 101 和 102 都已经提交,所以 m_ids 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 name = 赵六
总结: 在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读
在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)
在 T4 情况下的版本链为
:在当前执行 select
语句时生成一个 Read View
,此时 m_ids
:[101,102] ,m_low_limit_id
为:104,m_up_limit_id
为:101,m_creator_trx_id
为:103
此时和 RC 级别下一样:
DB_TRX_ID
为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 m_ids
列表中查找,发现 DB_TRX_ID
存在列表中,那么这个记录不可见DB_ROLL_PTR
找到 undo log
中的上一版本记录,上一条记录的 DB_TRX_ID
还是 101,不可见DB_TRX_ID
为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 name = 菜花
时间点 T6 情况下
:
在 RR 级别下只会生成一次Read View
,所以此时依然沿用 m_ids
:[101,102] ,m_low_limit_id
为:104,m_up_limit_id
为:101,m_creator_trx_id
为:103
DB_TRX_ID
为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 m_ids
列表中查找,发现 DB_TRX_ID
存在列表中,那么这个记录不可见DB_ROLL_PTR
找到 undo log
中的上一版本记录,上一条记录的 DB_TRX_ID
为 101,不可见DB_ROLL_PTR
找到 undo log
中的上一版本记录,上一条记录的 DB_TRX_ID
还是 101,不可见DB_TRX_ID
为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 name = 菜花
此时情况跟 T6 完全一样,由于已经生成了 Read View
,此时依然沿用 m_ids
:[101,102] ,所以查询结果依然是 name = 菜花
快照读
:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。当前读
:通过next-key锁(行锁+gap锁)来解决问题的。以上是脚本宝典为你收集整理的MVCC详解全部内容,希望文章能够帮你解决MVCC详解所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。