InnoDB 的 MVCC(多版本并发控制) 机制通过数据版本链与事务快照实现非锁定读,在高并发场景下提升性能并保证隔离性。以下是其核心工作原理:
一、MVCC 核心组件
- 隐藏字段
每行数据包含两个隐藏字段:DB_TRX_ID
(6字节):记录最近修改此行数据的事务 ID。DB_ROLL_PTR
(7字节):指向 Undo Log 的回滚指针,用于构建历史版本链。
注:行记录还包含
DB_ROW_ID
(隐含主键),若表无主键时自动生成。- 行版本链:每次数据修改时,通过Undo Log生成新版本,形成链式结构(当前数据→历史版本)
-
Undo Log
- 存储数据修改前的旧值(格式为链表),用于:
- 事务回滚时恢复数据。
- 构建 MVCC 的历史版本链。
- 更新数据时,旧值会被存入 Undo Log,新数据通过
DB_ROLL_PTR
指向旧版本。
- 存储数据修改前的旧值(格式为链表),用于:
-
ReadView(事务快照)
事务在第一次执行读操作时生成 ReadView,包含:m_ids
:生成 ReadView 时活跃(未提交)的事务 ID 列表。min_trx_id
:活跃事务中的最小事务 ID。max_trx_id
:生成 ReadView 时系统下一个事务 ID(即事务 ID 上限)。creator_trx_id
:创建该 ReadView 的事务 ID。-
注:可通过
information_schema.innodb_trx
监控事务状态
二、数据可见性判断规则
当事务读取某行数据时,按以下规则判断版本可见性:
- 检查数据行的
DB_TRX_ID
(修改该行的事务 ID)。 - 若
DB_TRX_ID == creator_trx_id
:- 当前事务修改的数据,可见。
- 若
DB_TRX_ID < min_trx_id
:- 该事务在 ReadView 创建前已提交,可见。
- 若
DB_TRX_ID >= max_trx_id
:- 该事务在 ReadView 创建后开启,不可见。
- 若
min_trx_id <= DB_TRX_ID < max_trx_id
:- 检查事务 ID 是否在活跃列表
m_ids
中:- 在列表中 → 事务未提交,不可见。
- 不在列表中 → 事务已提交,可见。
- 检查事务 ID 是否在活跃列表
- 若不可见:
- 通过
DB_ROLL_PTR
从 Undo Log 中取出旧版本数据,重复以上规则判断。
- 通过
可见性判断逻辑图
三、不同隔离级别的 MVCC 行为差异
隔离级别 | MVCC应用场景 | 锁机制辅助 |
---|---|---|
READ COMMITTED | 解决脏读,每次读新快照 | 行锁防止并发写冲突 |
REPEATABLE READ | 解决不可重复读,复用快照 | 间隙锁防止幻读 |
SERIALIZABLE | 禁用MVCC,完全依赖锁 | 表级锁保证串行化 |
- RC 级别:每次 SELECT 生成新 ReadView → 可读到其他事务已提交的数据。
- RR 级别:复用首次 SELECT 的 ReadView → 多次读结果一致(避免不可重复读)。
幻读的解决:
RR 级别通过 Next-Key Lock(记录锁 + 间隙锁) 阻止其他事务在范围内插入数据,而非 MVCC 本身。
四、二级索引的 MVCC 处理
- 二级索引无隐藏字段(无
DB_TRX_ID
和DB_ROLL_PTR
)。 - 判断可见性需两步:
- 通过索引找到主键值。
- 根据主键回表查询聚簇索引中的行记录,再通过其版本链判断可见性。
五、Purge 机制清理旧版本
- Purge 线程:定期清理不再需要的 Undo Log 历史版本。
- 清理条件:
- 数据版本对所有活跃事务均不可见时(即无 ReadView 依赖该版本)。
- 风险:长事务会阻塞历史版本清理 → 导致 Undo 表空间膨胀。
六、性能优化实践
- 控制长事务:避免Undo Log版本链过长(
SHOW ENGINE INNODB STATUS
监控) - 合理设计索引:减少间隙锁范围,降低锁冲突概率
- 参数调优:
innodb_max_purge_lag = 1000 # 控制Purge线程清理速度 innodb_undo_log_truncate = ON # 启用Undo Log自动清理
七、典型案例分析
场景:事务A(RR级别)查询账户余额为100元,事务B在此期间扣款50元并提交。
MVCC处理:
- 事务A的Read View中max_trx_id < 事务B的ID → 继续读取版本链中的旧数据(100元)
- 事务B的新版本数据(50元)对事务A不可见,保证可重复读
总结:MVCC 工作流程
- 写操作:
- UPDATE/DELETE 时生成 Undo Log,新数据通过
DB_ROLL_PTR
指向旧版本。
- UPDATE/DELETE 时生成 Undo Log,新数据通过
- 读操作:
- 生成 ReadView(RR 级别复用首次 ReadView)。
- 根据数据行的
DB_TRX_ID
+ ReadView 判断可见性。 - 若不可见,沿 Undo Log 版本链回溯至可见版本。
- 清理:
- Purge 线程回收无用的历史版本。
通过 MVCC,InnoDB 在 RC 和 RR 级别实现了高效的非锁定读,大幅减少锁竞争,同时保证事务隔离性。