概念:
MVCC,即多版本并发控制,是InnoDB实现高并发事务的核心机制。它的核心思想是:不通过加锁阻塞读写,而是为每一行数据维护多个历史版本,让读写事务可以无冲突地并发进行。这是InnoDB在"可重复读"和"读已提交"隔离级别下实现的关键。
解决问题
-
解决脏读:因为事务只能看到已提交事务产生的版本。
-
解决不可重复读(在"可重复读"级别下):因为在一个事务内 ,第一次快照读就创建 了ReadView,之后的所有快照读都复用这个ReadView,所以看到的数据 snapshot 是一致的。
当前数据行的关键数据结构
-
DB_TRX_ID(6字节):事务ID。记录最后一次插入或更新这行数据的事务ID。
-
DB_ROLL_PTR(7字节):回滚指针。指向这行数据上一个版本的undo log记录地址,是构成版本链的关键。
-
DB_ROW_ID(6字节):行ID。如果表没有主键,InnoDB会用它生成聚簇索引。
Undo log
-
每次修改数据时(INSERT/UPDATE/DELETE),都会在undo log中记录一条逻辑相反的日志,用于回滚和构建历史版本。
-
UPDATE操作会产生一个"更新日志",其中包含了被修改前的数据镜像,并通过DB_ROLL_PTR串联起来,形成一条单向链表,即"版本链"。链头是最新数据,链尾是最老数据。
链表节点存储数据
python
class UndoLogNode():
def __init__(tri_id, preNode, data):
self.data = data # 该行数据被修改前的完整内容
self.tri_id = tri_id # 该记录产生时对应的事务ID
self.pre_node = preNode or None # 指向更早一个版本的Undo Log记录的指针
ReadView
在事务执行第一个快照读时,根据瞬间截取的当时数据库系统的一个全局状态快照创建,它定义了当前事务能看到哪些版本的数据。
数据结构
-
m_ids:生成ReadView时,系统中活跃的(未提交的)读写事务ID列表。这个列表是从全局事务表中实时获取的。
-
min_trx_id:m_ids中的最小值。由 m_ids列表直接计算得出
-
max_trx_id:生成ReadView时,系统应该分配给下一个事务的ID。通常是当前的全局事务 ID (trx_sys->max_trx_id) + 1。
-
creator_trx_id:创建该ReadView的事务自己的ID。
工作流程
查询时
-
定位数据行:首先找到数据页中最新的那条记录。
-
遍历版本链:顺着该记录的DB_ROLL_PTR指针,在undo log中遍历它的历史版本链表。
-
可见性判断(核心规则):
-
拿每个版本中的DB_TRX_ID(即产生该版本的事务ID),与当前ReadView进行比对:
-
如果 DB_TRX_ID< min_trx_id,说明这个版本是在当前事务开始前就已提交的,可见。
-
如果 DB_TRX_ID>= max_trx_id,说明这个版本是在当前事务开始后才开启的,不可见,继续找更老的版本。
-
如果 min_trx_id<= DB_TRX_ID< max_trx_id,则需要判断DB_TRX_ID是否在m_ids(活跃事务列表)中:
-
如果在,说明生成该版本的事务在ReadView创建时还未提交,不可见。
-
如果不在,说明生成该版本的事务在ReadView创建时已提交,可见。
-
-
-
返回数据:找到第一个对当前事务可见的版本,将其数据返回。如果遍历完链都没找到,则说明此行对当前事务不可见。
MVCC工作流程
- 准备工作(由 InnoDB 自动维护)
- Undo Log 记录历史
- 构建版本链:表中的当前数据行,其隐藏字段 DB_ROLL_PTR指向了最近一次修改它所产生的 Undo Log 记录。
- 创建裁判规则ReadView
- ReadView执行其工作流程