中 进阶
undo-log与事务回滚#
一句话答案#
undo log 记录数据修改前的值,用于事务回滚(INSERT→DELETE)和 MVCC 版本链的快照读。
核心要点
1. undo log 记什么(按操作类型):
| 操作 | undo 记录内容 | 回滚时怎么做 | 提交后 |
|---|---|---|---|
| INSERT | 新行的主键 | 按主键删掉这行 | insert undo 只用于回滚,事务提交后就能直接释放 |
| DELETE | 旧行内容(原记录只打删除标记) | 去掉删除标记 | update undo 还要给 MVCC 用,等 purge 清理 |
| UPDATE | 被修改列的旧值 | 把旧值写回 | 同上 |
2. 版本链:
- 聚簇索引每行有隐藏列
DB_TRX_ID(最后修改它的事务 ID)和DB_ROLL_PTR(指向上一版本的 undo 记录) - 每次修改都生成一条 undo,并用 roll_pointer 串起来 → 形成版本链
- 快照读拿 Read View 沿版本链往回找第一个可见的版本(详见 MVCC实现原理)
3. 存储位置(MySQL 8.0):
- 实例初始化默认建 2 个 undo 表空间
innodb_undo_001/innodb_undo_002(不再放在系统表空间 ibdata1) - 每个 undo 表空间默认 128 个回滚段;8.0.14 起
innodb_undo_tablespaces弃用,改用CREATE UNDO TABLESPACE在线加 innodb_undo_log_truncate默认 ON:undo 表空间超过innodb_max_undo_log_size(默认 1GB)会被自动截断- undo 页本身的修改也写 redo log,崩溃恢复时先用 redo 恢复 undo,再用 undo 回滚未提交事务
4. 清理(purge):
- 事务提交后 update undo 挂到 history list,等所有活跃 Read View 都不再需要这些版本时,由 purge 线程回收,并物理删除打了删除标记的行
- 长事务 / 长时间不关的 Read View 会让 history list 越积越长,undo 表空间膨胀、版本链变长拖慢查询
面试回答(2分钟版)
undo log 是 InnoDB 引擎层的逻辑日志,记录数据修改前的逆操作:INSERT 记一条 DELETE,UPDATE 记旧值。它有两个核心作用:第一是保证事务原子性,事务回滚时按 undo log 执行逆操作恢复原始数据;第二是支撑 MVCC,多个版本的 undo log 串成版本链,快照读通过 ReadView 沿版本链找到自己可见的数据版本,实现非锁定读。和 redo log 的区别在于:redo log 是物理日志用于崩溃恢复保证持久性,undo log 是逻辑日志用于回滚和多版本读保证原子性和隔离性。需要注意的是 undo log 不会在事务提交后立即删除,因为其他事务的快照读可能还需要访问这些旧版本,要等到没有活跃事务引用时才由 purge 线程清理。这也是长事务有害的原因——它会导致 undo log 持续堆积无法回收,占用大量存储空间。
追问与易错
追问方向:
- “undo log 什么时候被清理?”→ insert undo 只用于回滚,提交后即可释放;update undo 事务提交后不会立即清理,要等到没有任何活跃事务的 Read View 引用该版本时,由 InnoDB 的 purge 线程异步回收
- “长事务为什么有害?”→ 长事务持有的 Read View 导致 undo log 版本链无法被 purge 清理,持续堆积占用大量存储;同时长时间持有行锁增加锁冲突和死锁概率
- “undo log 和 MVCC 什么关系?”→ undo log 通过 roll_pointer 串成版本链,MVCC 的快照读沿版本链查找,结合 Read View 的可见性规则找到当前事务应该看到的数据版本,实现非锁定一致性读
易错点:
- ❌ undo log 就是备份——是逻辑逆操作日志
- ❌ 事务提交后 undo 立即删——update undo 其他事务可能还在读(insert undo 例外,提交后即可释放)