这是《MySQL 8.0 源码与内核实战》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Undo log 记录行的旧版本,用于事务回滚和 MVCC 一致性读。旧版本通过版本链关联,长时间活跃事务会阻止 purge 清理。
18.1 事务 ID
BEGIN
-> first read snapshot
-> modifying statement
-> assign trx_id / roll_ptr
-> write undo
-> modify row
聚簇索引记录中的隐藏字段:
| 字段 | 含义 |
|---|---|
DB_TRX_ID |
最近修改事务 |
DB_ROLL_PTR |
指向 undo 记录 |
| hidden row id | 无主键表使用 |
18.2 Undo 类型
| 类型 | 场景 |
|---|---|
| insert undo | 插入回滚 |
| update undo | 更新或删除回滚与 MVCC |
INSERT 旧版本对一致性读不可见,回滚后可较快清理;UPDATE/DELETE undo 需要服务快照读取。
18.3 回滚
ROLLBACK
-> scan undo records
-> restore old values
-> undo secondary index changes
-> write redo for rollback
-> finish trx
大事务回滚耗时与修改量相关,可能持续产生 IO 和锁等待。
18.4 MVCC
一致性读根据 read view 判断版本可见性:
current record
-> visible?
|-- yes: return
+-- no: follow DB_ROLL_PTR
-> old version
-> visible?
Read View 记录活跃事务集合:
- m_ids;
- min_trx_id;
- max_trx_id;
- creator_trx_id。
可见性判断核心:
| 条件 | 可见性 |
|---|---|
| trx_id == creator | 自己修改可见 |
| trx_id < min | 已提交早于快照,可见 |
| trx_id >= max | 快照后开启,不可见 |
| 在活跃列表 | 快照时未提交,不可见 |
18.5 RR 与 RC
| 隔离级别 | read view |
|---|---|
| REPEATABLE READ | 事务内一致性读快照通常较稳定 |
| READ COMMITTED | 每条一致性读语句创建新快照 |
锁读仍按锁规则执行,REPEATABLE READ 不等价于完全串行化。
18.6 长事务
长事务持有旧 read view:
long transaction active
-> old versions needed
-> purge cannot advance
-> history list grows
-> undo tablespace grows
排查:
SELECT trx_id, trx_state, trx_started, trx_rows_modified
FROM information_schema.innodb_trx
ORDER BY trx_started;
SHOW ENGINE INNODB STATUS;
18.7 Undo 表空间
8.0 支持独立 undo 表空间管理:
SELECT * FROM information_schema.innodb_tablespaces
WHERE SPACE_TYPE='Undo';
可执行 truncate,但前提是 purge 能推进且无活跃事务依赖旧版本。
18.8 源码入口
| 文件 | 职责 |
|---|---|
trx0trx.cc |
事务生命周期 |
trx0rseg.cc |
回滚段 |
row0undo.cc |
行回滚 |
row0vers.cc |
版本可见性 |
read0read.cc |
Read View |
断点:
b ReadView::changes_visible
b trx_assign_read_view
b row_vers_build_for_consistent_read
本章小结
Undo 同时服务回滚和 MVCC。当前记录通过 roll pointer 连接历史版本,read view 决定可见性。长事务会延长版本链并阻塞 purge。
思考题
DB_ROLL_PTR的作用是什么?- INSERT undo 和 UPDATE undo 有什么差异?
- read view 如何判断版本可见?
- 长事务为什么导致 undo 膨胀?
- RC 和 RR 的 read view 创建时机有什么不同?