MVCC详解
作者:互联网
MVCC(Multi-Version Concurrency Control 多版本并发控制)
1 当前读、快照读
当前读
它读取的数据库记录,都是当前最新
的版本
,会对当前读取的数据进行加锁
,防止其他事务修改数据。是悲观锁
的一种操作。
如下操作都是当前读:
- select lock in share mode (共享锁)
- select for update (排他锁)
- update (排他锁)
- insert (排他锁)
- delete (排他锁)
- 串行化事务隔离级别
快照读
快照读的实现是基于多版本
并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本
的数据。
如下操作是快照读:
- 不加锁的select操作(注:事务级别不是串行化)
2 MVCC的实现原理
它的实现原理主要是隐藏字段
,undo log
,Read View
来实现的
1 隐藏字段
在内部,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 来生成聚簇索引
2 undo log
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线程统一清除
3 Read View
主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
主要有以下字段:
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
的事务 ID
4 可见性判断条件
-
db_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,你修改的数据,我当前事务也是看不见的。
5 MVCC 解决不可重复读问题
虽然 RC 和 RR 都通过 MVCC
来读取快照数据,但由于 生成 Read View 时机不同,从而在 RR 级别下实现可重复读
举个例子:
在 RC 下 ReadView 生成情况
-
假设时间线来到 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,所以导致不可重复读
在RR下ReadView生成情况
在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 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 = 菜花
- 时间点 T9 情况下:
此时情况跟 T6 完全一样,由于已经生成了 Read View
,此时依然沿用 m_ids
:[101,102] ,所以查询结果依然是 name = 菜花
6 解决幻读问题
快照读
:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。当前读
:通过next-key锁(行锁+gap锁)来解决问题的。
标签:事务,DB,id,详解,limit,MVCC,101,ID 来源: https://www.cnblogs.com/codespiderman/p/16184210.html