面试知识库
中 进阶

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 例外,提交后即可释放)