讲讲你对MVCC的理解
来源:讲讲你对MVCC的理解
兄弟们,去面中大厂的后端核心岗,MVCC (多版本并发控制) 绝对是区分“CRUD Boy”和“架构师”的分水岭。 面试官问你 MVCC,表面问的是概念,实则在考察你对“事务、锁、并发、存储引擎”的综合理解。
很多同学只能背出“快照读”,一旦被追问:“ReadView 到底什么时候生成?”、“Undo Log 既然是链表,什么时候清理?”、“我看不到这条数据,但我能 UPDATE 它吗?”,立马就会现原形。
今天 Fox 带你从源码级视角拆解 MVCC,助你面试降维打击!
⚔️ 第一部分:什么是 MVCC?(一句话定调)
话术模板:
“MVCC 的全称是多版本并发控制。它的核心思想是:通过维护数据的多个历史版本,让读操作去读历史快照,写操作去更新最新版本,从而实现读写并行,互不阻塞。”
核心价值:
- 读写解耦:写操作加锁(当前读),读操作不加锁(快照读)。
- 提升性能:极大地提高了数据库的高并发处理能力。
⚙️ 第二部分:MVCC 的“三驾马车”实现原理
要讲清楚 MVCC,必须把这三个组件讲透:隐藏字段、Undo Log、ReadView。
1. 隐藏字段(聚簇索引的秘密)
InnoDB 会在每行数据(聚簇索引)后面悄悄加三个字段:
- DB_TRX_ID(6字节):事务 ID,记录最近一次修改这行数据的事务 ID。
- DB_ROLL_PTR(7字节):回滚指针,指向 Undo Log 中这行数据的上一个版本。
- DB_ROW_ID:隐藏主键(如果表没有主键才会生成)。

🔥 高手加分点(二级索引): 面试官如果问:“二级索引也有这两个隐藏字段吗?” 你要回答:“没有!” 二级索引页头有个 PAGE_MAX_TRX_ID。如果这个 ID 小于当前事务的可见性水位,直接读;否则,需要回表到聚簇索引,利用聚簇索引上的 DB_TRX_ID 和 Undo Log 来做版本判断。
2. Undo Log(版本链)
当你更新一行数据时,InnoDB 不会直接覆盖旧数据,而是先把旧数据拷贝到 Undo Log 里。 DB_ROLL_PTR 就像一根链条,把新老数据串起来,形成一个版本链。链头是最新数据,链尾是最老数据。

3. ReadView(读视图)—— 裁判员
这是 MVCC 的灵魂!ReadView 是一个“快照切片”,决定了你到底能看到版本链里的哪一个版本。 核心字段:
- m_ids:生成 ReadView 时,当前系统中活跃(未提交)的事务 ID 列表。
- min_trx_id:活跃事务中最小的 ID(低水位)。
- max_trx_id:系统应该分配给下一个事务的 ID(高水位)。
- creator_trx_id:生成该 ReadView 的事务 ID(我自己)。

⚖️ 第三部分:数据的“可见性算法”(面试高频)

拿着行记录的 TRX_ID 去跟 ReadView 比对,规则如下:
- 自己改的 (
TRX_ID == creator_trx_id):✅ 可见。 - 已提交的旧事务 (`TRX_ID ):✅ 可见(你是过去式了)。
- 未来的新事务 (
TRX_ID >= max_trx_id):❌ 不可见(你是在我生成快照后才出现的)。 - 复杂的中间地带 (`min_trx_id ):
- 检查
TRX_ID是否在m_ids(活跃列表)里? - 在:说明还是“活跃状态”(未提交),❌ 不可见 -> 顺着 Undo Log 找上一个版本。
- 不在:说明虽然 ID 较大,但已经提交了,✅ 可见。
🥊 第四部分:RC 和 RR 的本质区别(进阶杀手锏)
面试官追问:“为什么 RC 能读到新提交的,而 RR 读不到?” 这是 P6 和 P7 的分水岭! 答案在于 ReadView 生成的时机不同。

RC(读已提交):
每次 SELECT 语句执行时,都会重新生成一个新的 ReadView。
所以,只要别的事务提交了,新的 ReadView 就能看到。
RR(可重复读):
⚠️ 纠偏细节:不是一开启事务就生成,而是在事务中执行第一条快照读(SELECT)时,生成一个 ReadView。
之后所有的 SELECT,都复用这同一个 ReadView。
这就实现了“可重复读”:不管外面怎么翻江倒海,我看的一直是刚开始的那个快照。
💣 第五部分:面试避坑指南(这才是高手的细节)
这里有三个只有架构师才会关注的细节,抛出来绝对震惊面试官:
1. MVCC 能完全解决幻读吗?
标准回答:
“MVCC 解决了快照读下的幻读,但没有解决当前读下的幻读。”
- 快照读(普通
SELECT):靠 ReadView 复用,看不到新插入的数据 -> 解决了。 - 当前读(
SELECT FOR UPDATE,UPDATE):必须读最新版。如果不加锁,别人插入新数据,你就能更新到,产生幻读。 - 最终解法:在 RR 级别下,当前读是通过 Next-Key Lock(临键锁) 锁住间隙来彻底解决幻读的。
2. “看不见”的数据,我能 UPDATE 吗?(诡异悖论)
场景:RR 级别,事务 A 插入并提交了一条记录。事务 B(在 A 提交前开启)SELECT 看不到这条记录(因为 ReadView 老)。 问题:事务 B 能执行 UPDATE 这条“看不见”的记录吗? 答案:能!而且更新完后,再次 SELECT 就能看见了!原理:
UPDATE是当前读,它不看 ReadView,直接读最新提交的数据,所以能更新成功。- 更新成功后,这条记录的
TRX_ID变成了事务 B 自己的 ID。 - 再次 SELECT 时,根据可见性规则“自己改的可见”,你就看到了!
3. Undo Log 会无限膨胀吗?(Purge 机制)
问题:历史版本这么多,磁盘不爆吗? 答案:不会。MySQL 有后台 Purge 线程。 当系统中最老的 ReadView 都不再需要某个事务版本之前的记录时,Purge 线程就会自动清理掉那些无用的 Undo Log。
延伸:这也是为什么我们千万不要在生产环境搞长事务!长事务会导致 ReadView 一直不关闭,Undo Log 无法回收,导致表空间极速膨胀,查询变慢。
🎓 第六部分:满分总结(背诵版)
最后,给面试官来个降维打击式的总结:
“MySQL 的 MVCC 本质上是空间换时间,通过维护Undo Log 版本链和ReadView,实现了读写并发。
它的核心在于:
- 结构上:利用聚簇索引的隐藏字段和 Undo Log 构建历史版本。
- 流程上:通过 ReadView 机制判断版本可见性。RC 级别是‘次次生成’,RR 级别是‘一次生成,终身复用’。
- 边界上:它解决了快照读的幻读,但当前读的幻读依然需要 Next-Key Lock 配合解决。同时,还要注意 Purge 机制回收日志,避免长事务导致系统膨胀。”

Fox 老师寄语: 兄弟们,这才是真正的底层原理。理解了这些,你不光能过面试,以后在生产环境排查死锁、慢查询、回滚空间膨胀这些问题时,你脑子里会有一幅清晰的图。 觉得有用的,点赞、收藏,下次面试前拿出来复习一遍,绝对稳!