MYSQL MVCC 多版本并发控制的原理?
在学习 InnoDB 并发控制之前,MVCC 是绕不过去的核心机制。
它既能实现“高并发读”,又能避免“加大量锁”。
-
• MVCC 是如何实现快照读的? -
• undo log、隐藏列、Read View 各自担什么责任? -
• 为什么 RR 能避免幻读而 RC 不能? -
• MVCC 和加锁有什么关系?
一、什么是 MVCC?一句话总结
MVCC(Multiversion Concurrency Control)多版本并发控制,是 InnoDB 为了提高读写并发而设计的机制。
核心思想:
写不阻塞读,读不阻塞写。
通过维护数据的多个版本,让读操作读取“过去的版本”,而不是最新版本。
它主要用于 快照读(无需加锁的读),例如:
SELECT * FROM user WHERE id = 1;
而非:
SELECT * FROM user WHERE id = 1 FOR UPDATE;
后者是当前读,需要加锁,不走 MVCC。
二、MVCC 的三大核心组件(决定你是否读到旧版本)
MVCC 依赖三样东西:
1)undo log(历史版本链)
每次 UPDATE、DELETE 时:
-
• 新值写到数据行 -
• 旧值写入 undo log -
• 并由 roll_pointer指向旧版本
形成一条链:
最新版本 → 上一版本 → 上上一版本 → ...
这是 MVCC 的“历史仓库”。
2)两列隐藏字段(trx_id、roll_pointer)
InnoDB 自动为每行维护两个隐藏列:
这两个字段是 MVCC 的基础。
3)Read View(可见性规则)
每个事务在执行快照读时,会生成一个 Read View,里面记录:
-
• 当前活跃未提交事务的 ID 列表(m_ids) -
• 最小未提交事务 ID(low_limit_id) -
• 最大已分配事务 ID(up_limit_id)
通过 Read View 判断一行数据是否对当前事务可见。
核心规则:
如果一个版本的 trx_id 在“已提交事务范围”内,则可见;否则不可见,需要沿着 undo log 继续往前找历史版本。
这是 MVCC 的灵魂。
三、MVCC 是如何实现“快照读”的?(核心原理)
假设表中某行有版本链:
v3 (trx_id=30)
↓
v2 (trx_id=20)
↓
v1 (trx_id=10)
事务 A(trx_id = 25)做快照读:
InnoDB 会:
-
1. 读取最新版本(trx_id = 30) -
2. 判断 trx_id=30 是否对事务 A 可见
→ 不可见(因为 trx_id 30 > 25) -
3. 读取 undo log → 版本 v2(trx_id = 20) -
4. 判断 trx_id=20 是否可见
→ 可见 -
5. 返回 v2
最终:
A 并不会看到最新提交的 v3,而是看到 v2(它开始时的世界)。
这就是“多版本”的意义。
四、MVCC 在不同隔离级别下的行为
MVCC 只在 RC(读已提交) 和 RR(可重复读) 下工作。
1)RC:每次 SELECT 都创建新的 Read View
效果:
-
• 每次都看到最新已提交版本 -
• 可能出现不可重复读
2)RR:同一个事务内只创建一次 Read View
效果:
-
• 每次 SELECT 的快照相同 -
• 能避免不可重复读 -
• 幻读通过 Next-Key Lock 解决,不是 MVCC
👉 RR 并不是靠 MVCC 避免幻读,而是靠锁。
五、MVCC 与锁的关系(非常容易考)
很多人混淆:
-
• MVCC 用于 快照读(普通 SELECT) -
• 锁用于 当前读(UPDATE / DELETE / FOR UPDATE)
对比:
所以:
MVCC 并不能解决所有并发问题,写操作仍然需要锁来保护。
六、MVCC 的实际意义(为什么这么优秀?)
1)高并发读
读操作不加锁 → 不阻塞写操作。
2)提升 OLTP 系统吞吐
普通 SELECT 基本不冲突,支持大量 QPS。
3)事务隔离简洁高效
RR 通过固定快照 + 锁实现一致性。
4)历史版本可以用于回滚
undo log 不仅用于 MVCC,还用于:
-
• 回滚事务 -
• 崩溃恢复
一举三得。
七、一个最经典的 MVCC 示例(理解后彻底通透)
场景
事务 A(trx_id = 100):
SELECT * FROM user WHERE id = 1;
事务 B(trx_id = 105):
UPDATE user SET age = 20 WHERE id = 1;
COMMIT;
事务 A 再查:
SELECT * FROM user WHERE id = 1;
行版本链:
v2 (trx_id=105) age=20
↓
v1 (trx_id=90) age=18
事务 A 的 Read View 看到:
-
• 90 < 100 → 已提交 → 可见 -
• 105 > 100 → 不可见
所以两次 SELECT 都读到:
age=18
即:可重复读实现了!
八、总结
InnoDB 的 MVCC 是通过 undo log 维护的多版本链、行的隐藏字段(trx_id、roll_pointer)以及 Read View 可见性规则实现的。
快照读通过 Read View 判断应该读取哪个版本,实现“读写不阻塞”。
RC 每次读都会生成新的 Read View;RR 同一个事务只生成一次。
MVCC 只用于普通 SELECT,当前读(UPDATE/DELETE/SELECT FOR UPDATE)必须加锁,不走 MVCC。

