Undo Log记录的是“反向操作指令”而非数据快照,如UPDATE生成“恢复旧值”的逻辑动作,INSERT回滚执行DELETE,DELETE回滚执行INSERT,UPDATE回滚执行反向UPDATE;其本质是逻辑日志,依赖原始数据未被purge,且需配合redo log确保回滚操作自身可恢复。

Undo Log 记录的是“怎么撤回”,不是“撤回快照”
很多人误以为原子性靠的是保存一份数据副本,等回滚时直接替换回去。其实 undo log 里存的不是旧数据本身,而是“反向操作指令”。比如执行 UPDATE t SET name = 'b' WHERE id = 1,undo log 记的不是 name = 'a' 这个值,而是类似“把 id = 1 行的 name 字段设为上一个值”的逻辑动作——它依赖原始字段是否还在页中(若已被 purge 就无法恢复)。
这意味着:
-
INSERT产生的undo log最轻量:回滚就是DELETE对应行 -
UPDATE/DELETE需要保留旧值上下文,所以 undo record 更大、更依赖 page 中未被覆盖的原始内容 - 事务未提交前,其他事务查不到它的修改,靠的是
read view+undo log构造一致性读,不是锁表或复制数据
ROLLBACK 不是“删脏页”,而是“再写一次旧值”
执行 ROLLBACK 时,InnoDB 并不会抹掉已写入 buffer pool 的脏页,而是顺着当前事务的 undo log 链,从最后一条记录往前逐条执行逆操作——这个过程本身也会生成新的 redo log,确保 rollback 操作自身可恢复。
常见误解:
- 以为回滚 = 把内存页清空或丢弃磁盘变更 → 实际是再次修改数据页,把新值覆盖回旧值
- 以为 undo log 可随时删除 → 它必须等到所有可能用到它的
read view都失效(即无活跃事务依赖该版本),才能被 purge 线程清理 - 只读事务也会长期 hold 住 undo log → 即使只执行
SELECT且不加锁,只要没COMMIT,就阻止 purge 清理旧版本
为什么不能只靠 Redo Log 或锁来保证原子性?
redo log 保证的是持久性:崩溃后能重放已提交事务的修改;但它不记录“怎么撤销”,所以无法支撑 ROLLBACK。而锁只控制并发访问顺序,不解决“执行一半失败后如何复原”的问题。
真正让原子性落地的关键配合是:
-
undo log提供“撤回能力”,按行记录语义级逆操作 -
redo log保证undo log自身写入不丢失(即 rollback 过程也可恢复) -
TRX_UNDO_INSERT_TRX和TRX_UNDO_UPDATE_TRX两类 segment 分开管理,避免混写干扰
没有 undo log,ROLLBACK 就只能靠应用层补偿,数据库层就失去了“执行中可中断、失败即归零”的确定性。
长期运行事务会让 undo log 膨胀,但原因常被忽略
现象如 show engine innodb status 显示 PURGE DONE FOR TRX'S 明显滞后,或 information_schema.INNODB_TRX 里有 TRX_STATE = RUNNING 超过几分钟的事务——这往往不是 DML 太多,而是某个只读事务迟迟不提交。
根本原因是:
- purge 线程不敢清理任何被活跃
read view引用的 undo log 版本 -
innodb_max_purge_lag是软限制,不会 kill 事务,只会拖慢后续 DML 的速度 - 即使事务只做
SELECT,只要没COMMIT,它就持续持有 read view,锁死对应范围的 undo log 生命周期
所以最隐蔽的原子性风险,不在写多,而在“读完忘了提交”。


















