MVCC(Multi-Version Concurrency Control,多版本并发控制) 在数据库(尤其是 MySQL InnoDB 引擎)里的底层实现原理,按存储结构 → 版本链 → 可见性判断 → 清理机制 这几个核心环节拆开讲,方便你作为 Java 高级开发工程师去理解。
1. 存储结构:隐藏列 + Undo Log
InnoDB 在每行记录中,除了业务字段,还会自动维护 3 个隐藏列:
隐藏列 | 作用 |
---|---|
DB_TRX_ID | 最后一次修改该行的事务 ID |
DB_ROLL_PTR | 指向该行 Undo Log 的指针,用来找到旧版本 |
DB_ROW_ID | 如果表没有显式主键,InnoDB 自动生成的唯一行 ID |
Undo Log 记录了修改前的数据快照,不仅存值,还会指向上一个版本的 Undo Log,形成版本链。
2. 版本链:历史版本的串联
举例,某行 name='Tom'
被 3 个事务依次修改:
V3: name='Alex' DB_TRX_ID=30 → ROLL_PTR → Undo Log V2
V2: name='Jerry' DB_TRX_ID=20 → ROLL_PTR → Undo Log V1
V1: name='Tom' DB_TRX_ID=10 → ROLL_PTR → null
这样,数据页只保存最新版本(V3),历史版本全部在 Undo Log 中,按时间反向串联。
3. 可见性判断:ReadView
ReadView 是事务在执行第一次 SELECT
时生成的快照规则,里面主要包含 4 个关键字段:
m_ids
:当前活跃事务 ID 列表(未提交)min_trx_id
:m_ids
中最小的事务 IDmax_trx_id
:当前已分配的下一个事务 ID(所有 < 该值的事务都已开始)creator_trx_id
:当前事务自己的 ID
判断规则(可见性算法):
-
如果
版本的 trx_id < min_trx_id
→ 可见(修改事务已提交) -
如果
版本的 trx_id >= max_trx_id
→ 不可见(事务在快照之后才开始) -
如果
min_trx_id <= trx_id < max_trx_id
:- 如果
trx_id ∈ m_ids
→ 不可见 - 否则 → 可见
- 如果
-
如果不可见,就沿着版本链
ROLL_PTR
去找上一个版本,直到找到可见版本或链尾。
4. 不同隔离级别下的 ReadView 行为
隔离级别 | ReadView 生成时机 | 结果特点 |
---|---|---|
READ COMMITTED | 每次执行 SELECT 都重新生成 ReadView | 同一事务多次查询可能看到不同版本 |
REPEATABLE READ(InnoDB 默认) | 第一次 SELECT 时生成,并在事务内复用 | 保证可重复读,防止不可重复读 |
5. 清理机制:Purging
- Undo Log 清理:当没有事务需要访问某个旧版本时,该 Undo Log 可被 purge 线程回收。
- 事务提交并不等于立即删除旧版本,因为其他事务可能还在用它做快照。
6. MVCC 的核心目的
- 提升并发性能:大部分读操作无需加锁,减少读写冲突。
- 实现一致性视图:读操作能读到事务开始时的数据快照(Repeatable Read)。
- 支持事务回滚:Undo Log 存的旧值是回滚的依据。
💡 一句话总结底层实现:
InnoDB MVCC 是通过 每行的事务 ID + Undo Log 版本链 + ReadView 可见性规则 实现的,让读操作可以在不加锁的情况下读取到事务一致性视图,并且配合 Purge 线程清理过期版本。