MySQL 中 MVCC 的底层原理详解(InnoDB 引擎)
MVCC 全称 Multi-Version Concurrency Control(多版本并发控制),是 InnoDB 引擎实现高并发的核心技术之一。它允许读写操作互不阻塞,大大提升了数据库的并发性能。
1. MVCC 解决的核心问题
在没有 MVCC 之前:
- 读操作会加共享锁(S 锁)
- 写操作会加排他锁(X 锁)
- 读写互相阻塞 → 并发度低
MVCC 的目标:实现“读不阻塞写,写不阻塞读”,同时保证事务隔离级别(重点是 可重复读)。
2. MVCC 的底层实现机制
2.1 隐藏字段(每行记录都有的系统列)
InnoDB 在每行记录中额外存储以下隐藏字段:
| 字段名 | 长度 | 作用 |
|---|---|---|
| DB_TRX_ID | 6 字节 | 事务 ID:最后修改该行的事务 ID |
| DB_ROLL_PTR | 7 字节 | 回滚指针:指向 Undo Log 的指针,用于找历史版本 |
| DB_ROW_ID | 6 字节 | 行 ID:当表没有主键时,InnoDB 自动生成的聚簇索引 ID |
2.2 Undo Log(回滚日志)
- 当事务修改一行记录时,InnoDB 会把旧版本写入 Undo Log。
- 新版本的
DB_ROLL_PTR指向旧版本。 - 形成一条版本链(从最新版本通过回滚指针指向越来越老的版本)。
版本链结构示例:
当前记录 (TRX_ID=100) → Undo Log (TRX_ID=90) → Undo Log (TRX_ID=80) → ...
3. ReadView(读视图)—— MVCC 的灵魂
ReadView 是每个 SELECT 操作(快照读)在开始时生成的一个一致性视图,它决定了当前事务能看到哪些版本。
ReadView 主要包含以下信息:
- m_ids:当前活跃(未提交)的事务 ID 列表
- min_trx_id:活跃事务中最小的 ID
- max_trx_id:下一个要分配的事务 ID(当前系统最大事务 ID + 1)
- creator_trx_id:创建该 ReadView 的事务 ID
版本可见性判断规则(非常重要):
当事务读取一行记录时,会从最新版本开始,沿着版本链遍历,找到第一个对当前事务可见的版本:
- 如果记录的
TRX_ID<min_trx_id→ 可见(在 ReadView 创建前已提交) - 如果记录的
TRX_ID==creator_trx_id→ 可见(当前事务自己修改的) - 如果记录的
TRX_ID在m_ids列表中 → 不可见(活跃事务,未提交) - 如果记录的
TRX_ID>=max_trx_id→ 不可见(在 ReadView 创建后才启动的事务)
找到可见版本后,直接返回该版本的数据。
4. 快照读 vs 当前读
| 读取方式 | SQL 示例 | 是否使用 MVCC | 说明 |
|---|---|---|---|
| 快照读 | SELECT * FROM table | 是 | 普通 SELECT,使用 MVCC 读历史版本 |
| 当前读 | SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEINSERT、UPDATE、DELETE | 否 | 读取最新版本,并加锁 |
可重复读(RR)隔离级别下,同一个事务中所有普通 SELECT 都使用同一个 ReadView,从而实现可重复读。
5. MVCC 在不同隔离级别下的行为
| 隔离级别 | ReadView 创建时机 | 说明 |
|---|---|---|
| 读未提交 | 每次 SELECT 都新建 ReadView | 几乎无 MVCC 效果 |
| 读已提交 | 每次 SELECT 都新建 ReadView | 每次读都能看到最新已提交数据 |
| 可重复读(默认) | 事务第一次 SELECT 时创建,整个事务复用 | 同一个事务内数据一致 |
| 串行化 | 不使用 MVCC,直接加锁 | 最严格 |
6. MVCC 的工作流程完整示例
假设事务 A(ID=100)执行:
START TRANSACTION;
SELECT * FROM user WHERE id = 1; -- 生成 ReadView
同时事务 B(ID=101)执行:
UPDATE user SET name = '新名字' WHERE id = 1; -- 创建新版本,旧版本进 Undo Log
事务 A 再次执行 SELECT:
- 看到的是旧版本(因为事务 B 未提交,ID=101 在 ReadView 的 m_ids 中)
- 事务 B 提交后,事务 A 再次 SELECT 依然看到旧版本(RR 隔离级别下 ReadView 不变)
7. MVCC 的优缺点
优点:
- 大幅提升并发度(读写不冲突)
- 实现非阻塞读
- 支持可重复读隔离级别
缺点:
- Undo Log 占用额外存储空间(需定期 purge)
- 版本链过长时,查找历史版本会有一定开销
- 无法完全解决幻读(MySQL 通过 Gap Lock + MVCC 共同解决)
8. 相关知识点扩展
- Undo Log 分为 Insert Undo 和 Update Undo
- Purge 线程:清理已无用的 Undo Log 版本
- 多版本并发控制只针对普通 SELECT(快照读)
- 聚簇索引中保存完整记录,二级索引中保存主键 + TRX_ID 等
总结:
MySQL 的 MVCC 核心就是 “隐藏字段 + Undo 版本链 + ReadView 可见性判断”。通过为每一行维护多个版本,并让读操作通过 ReadView 选择合适的版本,实现高并发下的数据一致性。
需要我继续深入讲解以下哪个部分?
- MVCC + Gap Lock 如何共同解决幻读
- Undo Log 的物理结构与 Purge 机制
- RR vs RC 隔离级别下 MVCC 的实际差异(带案例)
- 源码层面(trx_sys、ReadView 结构体等)
随时告诉我!