★ InnoDB 是如何用 MVCC 实现隔离级别的?快照读与当前读有什么区别?
结论先行:MVCC(多版本并发控制)的核心是“读写不互斥”:每行记录带隐藏列 trx_id(最后修改它的事务 id)与 roll_pointer(回滚指针,指向 undo log 历史版本形成的版本链);读操作基于 Read View(活跃事务列表 + 高低水位)判断哪个版本对当前事务可见。快照读(普通 SELECT)不加锁、读历史版本;当前读(SELECT ... FOR UPDATE / UPDATE / DELETE / INSERT)必须读最新已提交版本并加行锁/间隙锁。隔离级别的差异,本质就是“Read View 的生成时机 + 是否加锁”。
可见性判断(Read View 规则简化)
- 版本 trx_id 小于 Read View 中最小活跃事务 id → 该事务早已提交,可见;
- 版本 trx_id 在活跃事务列表中,或大于最大已分配事务 id → 不可见,沿 undo log 版本链找更旧版本;
- trx_id 等于当前事务 → 自己修改的,可见。
快照读与当前读对比表
| 维度 | 快照读 | 当前读 |
|---|---|---|
| SQL | 普通 SELECT | FOR UPDATE / UPDATE / DELETE / INSERT |
| 加锁 | 不加锁 | 行锁/间隙锁/临键锁 |
| 读取内容 | 历史快照版本 | 最新已提交版本 |
| 并发能力 | 读写互不阻塞 | 写写互斥 |
隔离级别差异示例
-- 事务 A:SELECT * FROM t WHERE id = 1; -- 快照读,生成 Read View
-- 事务 B:UPDATE t SET v = 2 WHERE id = 1; COMMIT;
-- 事务 A:再次 SELECT 同一行
-- 可重复读 → 复用事务首个 Read View,仍看到旧版本 v=1
-- 读已提交 → 每次 SELECT 重新生成 Read View,看到新版本 v=2
MVCC 与锁的配合边界
- 快照读之间互不阻塞、读写互不排斥,这是 InnoDB 高并发读的根基;
- 当前读之间需要互斥:UPDATE/DELETE 拿行锁,范围条件在 RR 下再叠加间隙锁;
- 版本链并非无限长:历史版本依赖 undo log,长事务会让 undo 膨胀、拖慢 purge 与回滚段;
- 可见性判断别记反:trx_id 低于低水位且不在活跃事务列表中的版本才可见。
Read View 生成时机
RR:事务内第一个快照读生成 Read View → 整个事务复用 → 可重复读
RC:每条 SELECT 都新建 Read View → 能读到已提交新数据 → 读已提交
常见追问 / 记忆点
- 追问:可重复读下幻读怎么还可能发生?答:纯快照读路径一致;一旦混入当前读(如 FOR UPDATE),范围由间隙锁/临键锁兜底,两种路径规则不同。
- 追问:Read View 什么时候生成?答:读已提交每次快照读都新建;可重复读只在事务内第一次快照读时创建并复用。
- 记忆点:MVCC = 版本链 + Read View;隔离级别差异 ≈ “Read View 生成时机 + 是否加锁”。