MVCC(多版本并发控制)
-
MVCC叫做多版本并发控制,读取数据时通过一种类似快照的方式将数据保存下来,不同的事务会看到自己特定版本的数据,这样读锁和写锁就不冲突了,提高了数据库的读写效率。
-
MVCC主要是处理读请求的,这个读主要处理的是快照读,而不是当前读。
- 快照读(一致性非锁定读),是基于MVCC的,读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
- 当前读(锁定读),其实是一种悲观锁,需要去加锁。读取的是最新版本的数据,比如我们执行insert、update或delete的时候,都需要先读取数据,再去操作。
-
事务的
ACID
特性中的隔离性,除了通过锁来保证,也是通过 MVCC保证的。而且 MVCC是只在READ COMMITTED
(读已提交)和REPEATABLE READ
(可重复读)两个隔离级别下工作。其他两个隔离级别都和 MVCC不兼容,因为READ-UNCOMMITTED
(读取未提交) 总是读取新的数据,而SERIALIZABLE
(可串行化)会对所有读取的行都加锁。 -
MVCC实现的主要流程:在
InnoDB
存储引擎中,创建一个新事务后,执行每个select
语句前,都会创建一个快照Read View
,ReadView
中主要保存了 :m_creator_trx_id
(创建该ReadView的事务ID)m_ids
(活跃事务id列表:一个当前对本事务不可见的其他活跃事务id的列表)m_low_limit_id
(当前ReadView
出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见)m_up_limit_id
(活跃事务列表m_ids
中最小的事务 ID,如果m_ids
为空,则m_up_limit_id
为m_low_limit_id
。小于这个 ID 的数据版本均可见)
当用户在这个事务中要读取某个记录行时,
InnoDB
会将该记录行的隐藏字段DB_TRX_ID
(最后一次插入或更新该行的事务 id)与ReadView
中的一些变量及当前事务ID进行比较,判断是否满足可见性条件,规则如下:- 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的。
- 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5。
- m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的。
- 如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)。
- 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5。
- 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见。
- 在该记录行的隐藏字段
DB_ROLL_PTR
(回滚指针,指向该行的undo log
。如果该行未被更新,则为空)指针所指向的undo log
取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空。
-
通过ReadView生成策略的不同,从而实现不同的隔离级别。
- 读已提交隔离级别下的事务在每次查询的开始都会生成一个独立的
ReadView
。 - 而可重复读隔离级别下的事务在第一次查询的时候生成一个
ReadView
,之后的读都复用之前的ReadView
。
- 读已提交隔离级别下的事务在每次查询的开始都会生成一个独立的
-
补充:
InnoDB
存储引擎为每行数据记录添加了三个隐藏字段:DB_TRX_ID(6字节)
:表示最后一次插入或更新该行的事务 id。此外,delete
操作在内部被视为更新,只不过会在记录头Record header
中的deleted_flag
字段将其标记为已删除。DB_ROLL_PTR(7字节)
回滚指针,指向该行的undo log
。如果该行未被更新,则为空。DB_ROW_ID(6字节)
:如果没有设置主键且该表没有唯一非空索引时,InnoDB
会使用该 id 来生成聚簇索引。
undo-log:
undo log
主要有两个作用:- 当事务回滚时用于将数据恢复到修改前的样子
- 另一个作用是
MVCC
,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过undo log
读取之前的版本数据,以此实现非锁定读
在
InnoDB
存储引擎中undo log
分为两种:insert undo log
和update undo log
:-
insert undo log
:指在insert
操作中产生的undo log
。因为insert
操作的记录只对事务本身可见,对其他事务不可见,故该undo log
可以在事务提交后直接删除。不需要进行purge
操作 -
update undo log
:update
或delete
操作中产生的undo log
。该undo log
可能需要提供MVCC
机制,因此不能在事务提交时就进行删除。提交时放入undo log
链表,等待purge线程
进行最后的删除。
不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log
成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。
tip:以上为本人参考各类资料所整理的,若有大佬发现错误,请评论指出,感谢!