MySQL 的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种通过保留数据历史版本实现高并发的机制,其核心目标是 在保证事务隔离性的前提下,避免读写操作互相阻塞。以下是其核心原理的全解析(基于 InnoDB 引擎):
一、MVCC 的底层核心组件
- 隐藏字段
每行数据(聚簇索引)包含 3 个隐藏字段: 字段名 说明
DB_TRX_ID 最近修改该行的事务ID(插入/更新/删除时写入)
DB_ROLL_PTR 回滚指针(指向 undo log 中历史版本数据)
DB_ROW_ID 隐藏主键(当无主键时自动生成)
- Undo Log(回滚日志)
• 存储数据修改前的历史版本(链式结构)
• 每个历史版本都包含:DB_TRX_ID + DB_ROLL_PTR + 原始数据
• 示例:
当前行: [值=500, TRX_ID=200, ROLL_PTR→Undo Log A]
↓
Undo Log A: [值=300, TRX_ID=100, ROLL_PTR→Undo Log B]
↓
Undo Log B: [值=200, TRX_ID=50]
- Read View(读视图)
事务第一次执行查询时生成,用于决定哪些历史版本对当前事务可见,包含: 字段 说明
m_ids 当前活跃(未提交)的事务ID列表
min_trx_id m_ids 中的最小事务ID
max_trx_id 系统预分配的下一个事务ID(大于所有已存在的事务ID)
creator_trx_id 创建该 Read View 的事务ID
二、MVCC 的可⻅性判断算法
当某事务读取数据行时,按规则遍历版本链直到找到可见版本:
-
检查当前行版本号 TRX_ID
-
可见条件判断(优先级顺序): • 若 TRX_ID == creator_trx_id → 可见(当前事务自身修改)
• 若 TRX_ID < min_trx_id → 可见(该版本在事务开启前已提交)
• 若 TRX_ID >= max_trx_id → 不可见(该版本在事务开启后创建)
• 若 TRX_ID ∈ [min_trx_id, max_trx_id):
◦ TRX_ID ∈ m_ids → 不可见(该版本所属事务未提交)
◦ TRX_ID ∉ m_ids → 可见(该版本所属事务已提交)
-
若不可见,沿 ROLL_PTR 找到上一个版本重新判断
口诀:
自己改的 ✓ 我开启前提交的 ✓ 我开启后创建的 ✗ 同期间未提交的 ✗
同期间提交的 ✓
三、增删改查如何与 MVCC 交互
- SELECT 查询(快照读)
SELECT * FROM account; -- 默认使用快照读
• 生成 Read View
• 沿版本链找到符合 可见性规则 的数据版本
- 数据更新(INSERT/UPDATE/DELETE)
• INSERT:写入新行,填充当前事务 ID
• UPDATE:
- 将当前行拷贝到 undo log
- 更新数据 → 修改值 + 写入新 TRX_ID + 回滚指针指向旧版本
• DELETE:类似 UPDATE,标记行为 已删除(通过 TRX_ID 标记)
四、MVCC 如何实现不同隔离级别
隔离级别 MVCC 机制实现方式
读提交 (RC) 每次 SELECT 都生成新的 Read View(读到其他事务最新提交的数据)
可重复读 (RR) 只在第一次 SELECT 时生成 Read View(后续读复用该视图 → 实现快照隔离)
未提交读 (RU) 不使用 MVCC,直接读最新数据(含未提交的数据)
⚠️ 幻读的特殊处理
在 RR 级别下,MVCC + Next-Key Lock(间隙锁) 共同解决幻读: • 快照读(SELECT):MVCC 保证读取原始快照 → 避免旧幻读
• 当前读(SELECT FOR UPDATE):使用 Next-Key Lock 锁住可能插入的范围 → 防止新写入
五、生产环境中的 MVCC 关键问题
- 长事务的危害
• Undo log 无法及时清理 → 版本链过长导致查询性能下降
• 大事务阻塞 Purge 线程 → 磁盘空间暴涨(ibdata1 膨胀)
解决方案:
• 监控 SELECT * FROM information_schema.innodb_trx;
• 设置 innodb_undo_log_truncate = ON
- 读不⻅最新数据的场景
事务 A 快照读后,事务 B 提交更新 → A 的后续读仍无法看到 B 的修改(RR 级别)。
解决方案:
SELECT * FROM account FOR SHARE; -- 加共享锁(走当前读)
- 版本清理机制
• 后台 Purge 线程清理无用的 undo log
• 清理条件:版本链中所有记录对当前任何活跃事务都不可⻅
六、MVCC 工作全过程示意图
sequenceDiagram participant T1 as 事务A (TRX_ID=100) participant T2 as 事务B (TRX_ID=200) participant DB as 数据行 + Undo Log
T1->>DB: UPDATE account SET balance=300 (原值200)
DB-->>T1: 创建 undo log1, 回滚指针指向旧值
T1->>DB: 更新行 TRX_ID=100
T2-->>DB: 启动事务,生成 Read View (活跃事务=[100])
T2->>DB: SELECT balance
DB-->>T2: 检查 TRX_ID=100 (活跃事务ID中 → 不可见)
DB-->>T2: 沿指针读 undo log1 值200 (TRX_ID=50 < min_trx_id → 可见)
T2-->>T2: 返回 balance=200
T1->>T1: COMMIT 提交事务
T2->>DB: SELECT balance (复用原Read View)
DB-->>T2: 仍通过 undo log1 读到200 (保证可重复读)
总结:MVCC 的价值
- 读写无阻塞:读操作不阻塞写,写操作不阻塞读
- 提升并发能力:RC/RR 级别下大幅减少锁争用
- 快速回滚:基于 undo log 轻松实现事务回滚
- 隔离性保障:灵活支持不同级别的事务隔离需求
⚠️ 注意:MVCC 仅优化读操作,并发写依然需要锁机制(如行锁、间隙锁)。