★ 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普通 SELECTFOR 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 生成时机 + 是否加锁”。
笔记加载中…