MySQL 专题(四):MVCC(多版本并发控制)原理深度解析
MySQL 专题(四):MVCC(多版本并发控制)原理深度解析
在上一篇文章中,我们提到 事务隔离级别 可以解决脏读、不可重复读和幻读问题。那么问题来了:
👉 在高并发场景下,如果每个查询都用锁来保证一致性,性能会不会大幅下降?
答案是:会!
因此 InnoDB 引入了一种更高效的机制 —— MVCC(Multi-Version Concurrency Control,多版本并发控制),它通过“数据多版本快照”来减少加锁,从而在 读多写少 的场景下实现高性能。
一、MVCC 的核心思想
MVCC 的目标是:
- 读操作几乎不用加锁,就能读到符合隔离级别的数据。
- 写操作尽量不阻塞读操作。
实现方式:
-
InnoDB 在每一行数据后面都隐藏存了两个额外字段:
- trx_id(事务 ID):最近一次修改该行的事务 ID。
- roll_pointer(回滚指针):指向 Undo Log(回滚日志)的地址,可以通过它找到历史版本数据。
👉 这样,InnoDB 就能通过回滚日志来“拼凑”出事务开始时的数据快照。
二、Undo Log 与数据快照
举个例子:
-
表
user
里有一行数据:id = 1, name = 'Tom'
-
事务 A 修改:
UPDATE user SET name='Jerry' WHERE id=1;
- InnoDB 会先写 Undo Log:保存
name='Tom'
的旧版本。 - 然后把数据改成
'Jerry'
,并记录新的事务 ID。
- InnoDB 会先写 Undo Log:保存
-
事务 B 此时如果需要“读已提交”,就会根据 Undo Log 还原
'Tom'
。
👉 Undo Log 就像“时光机”,帮助我们看到过去的数据版本。
三、ReadView(读视图)
MVCC 的关键是 ReadView,它决定了事务在某个时刻能看到哪些数据。
当事务执行 SELECT
时,会生成一个 ReadView,其中包含:
- m_ids:当时活跃事务的 ID 列表。
- min_trx_id:最小的活跃事务 ID。
- max_trx_id:下一个将被分配的事务 ID(即最大事务 ID + 1)。
- creator_trx_id:生成这个 ReadView 的事务 ID。
可见性规则
当读取一行记录时,根据行的 trx_id
与 ReadView 的事务 ID 范围比较:
- 如果
trx_id < min_trx_id
→ 已提交,数据可见。 - 如果
trx_id >= max_trx_id
→ 未来事务,数据不可见。 - 如果
trx_id
在m_ids
里面 → 事务还没提交,不可见。 - 否则 → 可见。
👉 通过这个机制,不同事务在同一时刻看到的数据版本可能不一样,这就是“多版本”。
四、不同隔离级别下的 MVCC
1. 读已提交(RC)
- 每次执行
SELECT
,都会生成一个新的 ReadView。 - 事务 A 可能两次读取到不同结果。
2. 可重复读(RR)
- 在事务开始时生成 ReadView,整个事务期间保持不变。
- 事务 A 无论执行多少次
SELECT
,都能看到一致的数据。
👉 这就是为什么 MySQL 默认使用 RR,同时还能避免“不可重复读”。
3. 串行化(Serializable)
- 完全依赖锁,不使用 MVCC。
五、MVCC 与幻读
很多同学会问:
MVCC 能不能解决幻读?
答案是:不能完全解决。
- MVCC 保证了“可重复读”,但不能阻止其他事务插入新行(幻影数据)。
- 为了解决幻读,InnoDB 在 RR 隔离级别 下,还引入了 间隙锁(Gap Lock) 和 临键锁(Next-Key Lock)。
六、MVCC 实际应用
-
避免长事务
- 长事务会导致 Undo Log 不断增长,因为历史版本不能被清理。
- 建议:大事务拆分成小事务,避免长时间持有 ReadView。
-
合适的隔离级别
- OLTP 系统(高并发业务系统):用 RC 提高性能。
- 对一致性要求极高的场景(比如金融交易):用 RR 或 Serializable。
-
理解快照读与当前读
- 快照读(普通 SELECT):走 MVCC,不加锁。
- 当前读(SELECT … FOR UPDATE / LOCK IN SHARE MODE):走锁机制,保证读到最新数据。
七、总结
- MVCC 的核心 = Undo Log + ReadView。
- 快照读 依赖 MVCC,可以高并发下无锁读取。
- 可重复读(RR) 的实现依赖固定的 ReadView。
- 幻读问题 仍需 间隙锁 来解决。
👉 一句话总结:
MVCC 让 MySQL 在“读多写少”的场景中,既能保证一致性,又能兼顾高性能,是 InnoDB 的灵魂设计之一。