在 MySQL 面试中,事务隔离级别和 MVCC(多版本并发控制)几乎是必问的考点。很多候选人对“可重复读”“幻读”等概念倒背如流,但一旦追问“MVCC 底层怎么实现的”“Read View 里到底存了什么”,往往就答不上来了。这篇文章从实战和面试的角度,把这两个知识点彻底讲清楚。
一、事务隔离级别:四种级别与三种读问题
SQL 标准定义了四种隔离级别,MySQL 的 InnoDB 引擎全部支持:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED (RC) | 不可能 | 可能 | 可能 |
| REPEATABLE READ (RR) | 不可能 | 不可能 | InnoDB 下基本不可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
三种读问题的本质区别在于**“两次读之间,其他事务做了什么”**:
- 脏读:读到了其他事务未提交的数据。
- 不可重复读:同一行数据两次读取结果不同(其他事务提交了 UPDATE)。
- 幻读:同一范围两次查询,第二次多出或少了一些行(其他事务提交了 INSERT/DELETE)。
MySQL 的默认隔离级别是 REPEATABLE READ。值得注意的是,标准 RR 并不能防止幻读,但 InnoDB 通过 Next-Key Lock(间隙锁 + 行锁) 在 RR 下基本解决了幻读问题。
二、MVCC 的核心概念
MVCC(Multi-Version Concurrency Control)的核心思想是:读操作不加锁,通过读取历史版本实现一致性读。它依赖三个隐藏组件:
1. 隐藏字段
InnoDB 为每行记录添加两个隐藏列:
- DB_TRX_ID(6 字节):最近一次修改该行的事务 ID。
- DB_ROLL_PTR(7 字节):回滚指针,指向 undo log 中的旧版本记录。
2. Undo Log 版本链
每次对记录做修改时,旧值会被写入 undo log,并通过 DB_ROLL_PTR 串联成一条版本链。链头是最新版本,链尾是最老的版本。
当前行 → undo log v3 → undo log v2 → undo log v1
3. Read View(读视图)
Read View 是 MVCC 实现一致性读的关键,它决定了一个事务能看到版本链中的哪个版本。Read View 包含四个核心字段:
- m_ids:生成 Read View 时,当前活跃(未提交)的事务 ID 列表。
- min_trx_id:m_ids 中的最小值。
- max_trx_id:生成 Read View 时,系统应分配给下一个事务的 ID(即当前最大事务 ID + 1)。
- creator_trx_id:创建该 Read View 的事务自身 ID。
三、可见性判断规则
拿到版本链中某个版本的 DB_TRX_ID 后,按以下规则判断是否可见:
- 若
trx_id == creator_trx_id:说明是自己修改的,可见。 - 若
trx_id < min_trx_id:说明该版本在 Read View 创建前已提交,可见。 - 若
trx_id >= max_trx_id:说明该版本在 Read View 创建后才开启,不可见。 - 若
min_trx_id <= trx_id < max_trx_id:- 若 trx_id 在 m_ids 中,说明事务仍活跃,不可见。
- 若不在 m_ids 中,说明已提交,可见。
如果当前版本不可见,就顺着 DB_ROLL_PTR 找上一个版本,直到找到可见版本或链尾。
四、RC 与 RR 的本质区别
这是面试的高频追问点:RC 和 RR 都基于 MVCC,为什么行为不同?
答案在于 Read View 的生成时机:
- READ COMMITTED:每次 SELECT 都生成一个新的 Read View。所以每次查询都能看到最新已提交的数据,导致不可重复读。
- REPEATABLE READ:只在事务第一次 SELECT 时生成一个 Read View,整个事务复用。所以后续查询看到的仍是同一快照,实现可重复读。
五、当前读 vs 快照读
MVCC 只作用于快照读(普通 SELECT)。以下操作属于当前读,读取的是最新版本,并加锁:
SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEINSERT/UPDATE/DELETE
在 RR 下,当前读配合 Next-Key Lock 来防止幻读。这也是为什么“RR 下 MVCC + 间隙锁”能基本解决幻读的原因。
六、常见面试追问
Q1:RR 下幻读真的完全解决了吗?
没有完全解决。快照读靠 MVCC 避免幻读,当前读靠间隙锁避免幻读。但如果在快照读之后执行了当前读(如 UPDATE),可能看到“多出来”的行,这就是所谓的“幻读的边界情况”。
Q2:undo log 什么时候删除?
当没有比该版本更早的 Read View 需要它时,由 purge 线程清理。长事务会阻止清理,导致 undo log 膨胀。
Q3:为什么不用锁实现一致性读?
加锁会阻塞写操作,并发性能差。MVCC 让读不加锁、写不阻塞读,极大提升了并发能力。
七、总结
一句话概括:MVCC 通过隐藏字段 + undo log 版本链 + Read View 实现无锁一致性读;RC 与 RR 的区别只在于 Read View 的生成时机。 理解这条主线,再结合当前读、间隙锁等概念,MySQL 事务相关的面试题基本就能从容应对了。
未经允许不得转载:任鹏个人博客 » MySQL 事务隔离级别与 MVCC 实现原理详解

