MVCC 完整读取演示

发布时间:2026/7/19 8:57:09
MVCC 完整读取演示 一、准备数据与事务环境假设有一张account表初始数据idnamebalanceDB_TRX_IDDB_ROLL_PTR1Alice1000100NULL当前活跃事务列表[]空当前最大事务 ID100undo log 版本链按时间顺序当前行 → (NULL)二、场景一演示 MVCC 解决脏读时间线T0 ── T1 ── T2 ── T3 ── T4 ── T5 ── T6 ── T7步骤时间事务 Aid101事务 Bid102数据库状态1T0BEGIN—balance1000, TRX_ID1002T1—BEGIN同上3T2—UPDATE account SET balance2000 WHERE id1balance2000, TRX_ID102, 旧版本(1000)→undo4T3SELECT balance FROM account WHERE id1—?5T4—ROLLBACKbalance1000 恢复6T5——仅 A 看到的结果保留T3 时刻MVCC 如何挡住脏数据事务 A 第一次 SELECT生成 Read Viewm_ids活跃事务列表 [102] min_trx_id 102 max_trx_id 103 creator_trx_id 101自己可见性判定读取最新行balance2000, DB_TRX_ID102判定102 ∈ m_ids活跃中未提交→不可见沿DB_ROLL_PTR回溯到 undo logbalance1000, DB_TRX_ID100判定100 min_trx_id(102) →可见返回balance 1000结果A 读到 1000未被脏数据 2000 影响。T4 时刻 B 回滚后A 看到的 1000 仍然是真实值 →脏读被消除。三、场景二演示 MVCC 解决不可重复读时间线T0 ── T1 ── T2 ── T3 ── T4 ── T5 ── T6步骤时间事务 Aid101事务 Bid102数据库状态1T0BEGIN—balance1000, TRX_ID1002T1SELECT balance→ ?—A 第一次 SELECT3T2—BEGIN—4T3—UPDATE ... SET balance2000并 COMMITbalance2000, TRX_ID102, undo 保存 10005T4SELECT balance→ ?—A 第二次 SELECT6T5COMMIT——T1 时刻建立 Read ViewRR 级别下的快照m_ids [101] ← 仅 A 自己活跃 min_trx_id 101 max_trx_id 102 creator_trx_id 101可见性判定当前行TRX_ID100100 min_trx_id(101) →可见返回balance 1000✓T4 时刻复用同一个 Read ViewRR 的关键m_ids [101] ← 仍是 A 启动时的视图未刷新 min_trx_id 101 max_trx_id 102可见性判定当前最新行balance2000, DB_TRX_ID102判定102 ∈ m_ids(101)不在但 102 ≥ max_trx_id(102) 且 102 ≠ creator_trx_id →不可见回溯 undo logbalance1000, DB_TRX_ID100判定100 min_trx_id(101) →可见返回balance 1000✓与 T1 一致结果A 两次 SELECT 都读到 1000B 在中间提交了 2000 也没影响 →不可重复读被消除。四、对比RC 级别下同样的操作会发生什么如果把隔离级别降到Read Committed步骤RC 下的 Read View读取结果T1 第一次 SELECTm_ids[101]1000T3 B 提交—DB_TRX_ID102, balance2000T4 第二次 SELECT新建m_ids[]当前 102 不在活跃列表 →可见返回2000→RC 级别下不可重复读仍然存在这就是为什么 MVCC 在 RR 级别才能完整解决不可重复读。五、一张图看清整个流程┌──────────────────────────────┐ 事务 A 启动 ───► │ 建立 Read View快照固化 │ └──────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ 任何时刻 SELECT 任何时刻 SELECT │ │ 读最新行DB_TRX_ID102(B已提交) 读最新行DB_TRX_ID102(B已提交) │ │ 判定不可见不在A启动时 判定仍不可见RR复用视图 的活跃列表中但属于后续 │ │ 沿 undo 链回溯 沿 undo 链回溯 │ │ └───────────────┬───────────────┘ ▼ 返回 1000同一个旧版本六、核心要点回顾机制作用undo log 版本链保存历史快照让读旧值成为可能Read View定义哪些版本对当前事务可见的判定规则快照固化RR同一事务内 Read View 不变 → 同一行读到相同值屏蔽未提交已提交的事务 ID 必须在快照前出现 → 挡住脏数据一句话MVCC 通过版本链 读视图两套机制把脏读和不可重复读问题转化为按版本号查快照的纯读取问题从根本上绕开了读时要锁、写时要等的性能损耗。如果你想要的话我可以再演示MVCC 与幻读Phantom Read的关系——这是它没能完全解决的场景以及 InnoDB 是如何用Next-Key Lock补位的。