🔄 MySQL MVCC 原理详解

Multi-Version Concurrency Control 多版本并发控制

核心概念

📌 隐藏列

InnoDB 为每行记录添加了两个隐藏列:DB_TRX_ID(事务ID)和 DB_ROLL_PTR(回滚指针),用于实现多版本。

📜 Undo Log

记录数据修改前的值,形成版本链。每次修改都会生成一条 undo 记录,通过回滚指针串联起来。

👁️ Read View

读事务开始时创建的快照,记录当前活跃事务ID列表,用于判断数据版本的可见性。

🎯 可见性判断

根据 Read View 判断:数据的事务ID是否对当前事务可见,决定读取哪个版本的数据。

InnoDB 行记录结构

每一行数据背后,都隐藏着 MVCC 的关键信息:

id 主键
name 用户名
age 年龄
trx_id 事务ID
roll_ptr 回滚指针

💡 工作原理

DB_TRX_ID:记录最近一次修改该行的事务ID
DB_ROLL_PTR:指向 undo log 记录的指针,形成版本链的链表头

动态演示:事务并发场景

① 初始化数据
② 并发修改
③ 版本链形成
④ 可见性判断
T1
T2
T3
T4
📦 数据页 (Data Pages)
📜 Undo Log 链
初始值 (age=25)
trx_id: 100
[系统] 点击按钮开始演示 MVCC 工作流程...

可见性判断算法

🎯 核心规则

当事务读取一行数据时,通过 Read View 判断该行数据的某个版本是否可见:

IF (trx_id == creator_trx_id) THEN

// 自己修改的,可以看见

ELSE IF (trx_id < min_trx_id) THEN

// 提交过且比快照还早,可以看见

ELSE IF (trx_id > max_trx_id) THEN

// 快照创建后才开始的,看不见

ELSE IF (trx_id in m_ids) THEN

// 活跃事务修改的,看不见

→ 通过 roll_ptr 顺着版本链往下找...

隔离级别与 MVCC

🔒 READ COMMITTED

  • 每次读取都生成新的 Read View
  • 可避免脏读和不可重复读
  • 同一事务中两次读取可能不同
  • PostgreSQL 的默认级别

🔐 REPEATABLE READ

  • 事务开始时创建 Read View
  • 整个事务期间复用同一个快照
  • InnoDB 默认隔离级别
  • 解决脏读、不可重复读、幻读

📖 READ UNCOMMITTED

  • 不使用 MVCC
  • 读取最新版本(可能有脏数据)
  • 可能脏读,实际很少用

🏛️ SERIALIZABLE

  • MVCC + 锁机制
  • 读取时会加锁
  • 最强隔离,性能最差
  • 并发度最低

场景案例:余额修改

时间 事务A (转账) 事务B (查询) 账户余额 说明
T1 BEGIN - 1000 初始余额
T2 UPDATE SET balance=900 - - 事务A修改,未提交
T3 - BEGIN → SELECT 1000 读到了旧版本!
T4 COMMIT - 900 事务A提交
T5 - SELECT again 1000 RC下新快照,RR下仍1000
T6 - COMMIT 900 事务B提交,余额900

核心要点总结

✅ MVCC 优势

• 读不阻塞写,写不阻塞读
• 大幅提升并发性能
• 快照读避免幻读问题
• 减少锁争用

⚠️ 注意事项

• 需要定期清理旧版本(purge)
• 极端情况下可能产生长事务
• RR 级别下仍有幻读可能(需 gap lock)
• 只适用于快照读

🔍 适用场景

• 高并发读多写少
• 需要一致性快照
• 长事务处理
• 报表查询