MySQL MVCC多版本并发控制的实现原理
MySQL 的 InnoDB 引擎通过 MVCC(Multi-Version Concurrency Control,多版本并发控制)实现了在不加锁的情况下进行并发读写。这种机制让“读操作”和“写操作”互不冲突,极大地提升了数据库的性能。MVCC 的核心实现依赖于三个组件:隐藏字段、Undo Log 和 Read View。
1. 理解当前读与快照读
在深入 MVCC 之前,必须区分 MySQL 中两种截然不同的读取方式。
-
识别 当前读(Current Read)。
这种读取方式读取的是记录的最新版本,并且会对读取的记录加锁,保证其他事务不能并发修改。常见的操作包括:SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEINSERT、UPDATE、DELETE等写操作
-
识别 快照读(Snapshot Read)。
这种读取方式读取的是记录的可见版本(可能是历史版本),不需要加锁。普通的SELECT操作就是快照读(在 Read Committed 和 Repeatable Read 隔离级别下)。
MVCC 的作用就是让“快照读”能够读取到符合当前事务隔离级别的历史版本,而无需等待锁的释放。
2. 分析行记录的隐藏字段
InnoDB 在每行数据中,除了用户定义的字段外,还会自动添加几个隐藏字段。这些字段是构建版本链的基础。
-
查看 行记录结构。
每一行数据内部主要包含以下三个对用户不可见的字段:DB_TRX_ID:事务 ID。
记录最后一次插入或更新该行数据的Transaction ID(6 字节)。DB_ROLL_PTR:回滚指针。
指向该行记录的上一个版本存储在Undo Log中的位置(7 字节)。DB_ROW_ID:隐藏主键。
如果表中没有主键,InnoDB 会自动生成这个 ID(6 字节)。
其中,
DB_TRX_ID和DB_ROLL_PTR是 MVCC 的核心。
3. 构建版本链:Undo Log
当一个事务修改某行记录时,InnoDB 不会直接覆盖旧数据,而是将旧数据拷贝到 Undo Log 中。
-
执行 更新操作。
假设一个事务将某行的值从A改为B。- InnoDB 将 旧数据
A复制到 Undo Log 中。 - 更新 当前行记录的值为
B。 - 设置 当前行记录的
DB_TRX_ID为当前事务 ID。 - 设置 当前行记录的
DB_ROLL_PTR指向 Undo Log 中旧数据A的地址。
- InnoDB 将 旧数据
-
重复 修改过程。
如果另一个事务又将该行从B改为C,则会再次将B复制到 Undo Log,并修改当前行的DB_ROLL_PTR指向新的 Undo Log 记录。 -
形成 链表结构。
通过DB_ROLL_PTR,当前行记录和 Undo Log 中的历史版本串联成一个链表,称为“版本链”。链表头是最新记录,链表尾是最早的历史记录。
以下是版本链的简化逻辑图:
4. 判定可见性:Read View
有了版本链,还需要一个规则来判断当前事务应该看到链表中的哪个版本。这个规则就是 Read View(读视图)。
当事务发起快照读时,会生成一个 Read View,其中包含以下核心字段:
m_ids:生成 Read View 时,当前系统中活跃(未提交)的事务 ID 列表。min_trx_id:m_ids中最小的事务 ID。max_trx_id:生成 Read View 时,系统应分配给下一个事务的 ID(即当前最大事务 ID + 1)。creator_trx_id:生成该 Read View 的事务本身的 ID。
5. 执行版本可见性算法
当事务读取某行数据时,会拿到该行记录最新版本的 DB_TRX_ID,并与 Read View 进行比对,决定是否接受该版本。如果不接受,则沿着 DB_ROLL_PTR 遍历 Undo Log 版本链,直到找到一个可见的版本。
比对规则如下:
-
判断
DB_TRX_ID是否等于creator_trx_id。- 如果是,说明是自己修改的,可见。
-
判断
DB_TRX_ID是否小于min_trx_id。- 如果是,说明该版本在 Read View 生成前就已经提交了,可见。
-
判断
DB_TRX_ID是否大于或等于max_trx_id。- 如果是,说明该版本是在 Read View 生成后才开启的事务修改的,不可见。
-
判断
DB_TRX_ID是否在m_ids列表中。- 如果在,说明生成 Read View 时该事务还未提交(活跃状态),不可见。
- 如果不在,说明生成 Read View 时该事务已经提交了,可见。
如果该版本被判定为不可见,则 继续 检查 Undo Log 中的下一个历史版本,重复上述流程,直到找到可见版本或链表结束。
6. 区分 RC 和 RR 隔离级别的差异
MVCC 使得 InnoDB 可以在 Read Committed(RC,读已提交)和 Repeatable Read(RR,可重复读)两个隔离级别下工作。它们的区别在于 生成 Read View 的时机不同。
-
配置 Read Committed (RC) 级别。
- 执行 每一次 SELECT 语句时。
- 系统都会重新生成一个新的 Read View。
- 这意味着,如果期间有其他事务提交了修改,下一次 SELECT 生成的新 Read View 能够看到这些已提交的最新变更(因为旧事务 ID 不再活跃)。
-
配置 Repeatable Read (RR) 级别。
- 执行 事务中的 第一次 SELECT 语句时。
- 系统会生成一个 Read View,并在整个事务期间 一直复用 这个 Read View。
- 这意味着,无论期间其他事务是否提交了修改,由于 Read View 中的
m_ids等信息是固定的,判定规则永远不会变,从而保证了每次读取到的数据都是一致的。
| 特性 | Read Committed (RC) | Repeatable Read (RR) |
|---|---|---|
| 生成时机 | 每次查询时生成 | 事务开始后第一次查询时生成 |
| 复用机制 | 不复用,每次重新生成 | 整个事务期间复用同一个 |
| 可见性 | 只能看到已提交的数据 | 只能看到事务开始前已提交的数据 |
7. 总结核心流程
通过以上步骤,MVCC 的完整闭环逻辑如下:
- 开启 事务,发起查询。
- 生成 Read View(根据隔离级别决定是新生成还是复用)。
- 读取 行记录的
DB_TRX_ID。 - 比对 Read View 规则。
- 决策:
- 如果可见,返回 该行数据。
- 如果不可见,沿着
DB_ROLL_PTR去 Undo Log 中找旧版本。
- 循环 步骤 3-5,直到找到可见版本。
通过这种机制,MySQL 既保证了读写操作的并发执行,又实现了不同级别的隔离,避免了脏读和不可重复读(在 RR 级别下)。

暂无评论,快来抢沙发吧!