InnoDB 实现 MVCC(多版本并发控制)的机制主要依赖于 Undo Log(回滚日志)、Read View(读视图) 和 隐藏的事务字段。以下是具体实现步骤和原理:
1. 核心数据结构
InnoDB 的每一行数据(聚簇索引记录)包含两个隐藏字段:
DB_TRX_ID
:最近修改该行的事务 ID。DB_ROLL_PTR
:指向该行旧版本数据的回滚指针(即 Undo Log 的地址)。
2. Undo Log 与版本链
- Undo Log 的作用:
每次对数据进行修改(INSERT/UPDATE/DELETE),InnoDB 会生成 Undo Log,记录修改前的数据镜像。 - 版本链的构建:
通过DB_ROLL_PTR
字段,将同一行数据的多个版本按修改顺序链接成链表。
示例:当前行 → [版本3: trx_id=300, roll_ptr → 版本2] ↑ 版本2 → [trx_id=200, roll_ptr → 版本1] ↑ 版本1 → [trx_id=100, roll_ptr → NULL]
3. Read View 的生成
当事务执行 一致性读(如 SELECT
)时,InnoDB 为其生成一个 Read View,包含以下信息:
m_ids
:当前活跃(未提交)的事务 ID 列表。min_trx_id
:m_ids
中的最小事务 ID。max_trx_id
:下一个即将分配的事务 ID(即当前最大事务 ID +1)。creator_trx_id
:创建该 Read View 的事务 ID(仅当该事务自身有修改时存在)。
4. 数据可见性判断规则
对于某一数据行的版本,判断其对当前事务是否可见的规则如下:
- 版本的事务 ID <
min_trx_id
:
该版本已提交,可见。 - 版本的事务 ID ≥
max_trx_id
:
该版本由未来事务生成,不可见。 min_trx_id
≤ 版本的事务 ID <max_trx_id
:- 若版本的事务 ID 不在
m_ids
中,说明该事务已提交,可见。 - 若版本的事务 ID 在
m_ids
中,说明该事务未提交,不可见。
- 若版本的事务 ID 不在
- 版本的事务 ID =
creator_trx_id
:
该版本由当前事务自身修改,可见。
5. MVCC 的查询流程
- 定位最新数据行:通过聚簇索引找到当前行的最新版本。
- 遍历版本链:从最新版本开始,根据
DB_ROLL_PTR
回溯旧版本。 - 可见性检查:对每个版本应用 Read View 规则,找到第一个可见的版本。
示例:
假设事务 A(trx_id=200
)的 Read View 中:
m_ids = [100, 300]
min_trx_id=100
,max_trx_id=400
遍历某行的版本链:
- 版本3(trx_id=300):在
m_ids
中 → 不可见。 - 版本2(trx_id=200):等于
creator_trx_id
→ 可见(若事务 A 修改了该行)。 - 版本1(trx_id=100):在
m_ids
中 → 不可见。 - 版本0(trx_id=50):小于
min_trx_id
→ 可见。
最终事务 A 读取到版本0。
6. 不同隔离级别的实现差异
- 读已提交(Read Committed):
每次执行SELECT
时生成新的 Read View,看到已提交的最新数据。 - 可重复读(Repeatable Read):
事务内第一次SELECT
时生成 Read View,后续复用该视图,保证多次读取一致性。
7. Undo Log 的清理
- Insert Undo Log:事务提交后立即删除(不参与 MVCC)。
- Update/Delete Undo Log:需保留至所有可能访问旧版本的事务结束(通过
purge
线程异步清理)。
8. MVCC 与锁的协同
- 写操作(UPDATE/DELETE):
即使使用 MVCC,写操作仍需加锁(如行锁)避免脏写。 - 读操作(SELECT):
默认无锁,通过 MVCC 读取快照版本。若需锁定读,可加锁(如SELECT ... FOR UPDATE
)。
总结
InnoDB 通过以下机制实现 MVCC:
- 版本链:通过
DB_ROLL_PTR
链接数据的历史版本。 - Read View:判断事务可见性的依据。
- Undo Log:存储历史版本数据,支持版本链回溯。
- 隔离级别适配:通过动态生成或复用 Read View 实现不同隔离级别。
# MVCC 执行示例
事务A(trx_id=200)读取某行数据:
1. 找到最新版本(trx_id=300)。
2. 检查 Read View:m_ids=[100, 300], min=100, max=400。
3. trx_id=300 在 m_ids 中 → 不可见。
4. 回溯到版本2(trx_id=200),等于 creator_trx_id → 可见。