事务回滚不物理删除数据,而是通过undo log执行逻辑逆操作:INSERT回滚删主键行、DELETE回滚插入原数据、UPDATE回滚覆写旧值;undo log需先于数据落盘,按LIFO顺序解析应用,依赖purge线程清理。

事务回滚时,MySQL 真的“删数据”了吗?
不是。回滚不等于物理删除或覆盖,而是靠 undo log 做逻辑逆操作。比如你执行了 UPDATE t SET i1 = 502 WHERE id = 50,undo log 会记下这条记录修改前的值(比如 i1 = 501)和主键 id = 50;回滚时,InnoDB 不是把磁盘上那行再改回去,而是读取 undo log,生成一条反向 UPDATE:把 i1 改回 501。
这种设计避免了频繁磁盘寻址,也支撑了 MVCC —— 其他事务读取快照时,可能正依赖这条 undo log 链来获取旧版本。
-
INSERT回滚:undo log只存主键,回滚即执行DELETE对应主键行 -
DELETE回滚:undo log存整行原始数据,回滚即执行INSERT -
UPDATE回滚:记录被改字段的旧值,回滚即用旧值覆盖新值
undo log 写入时机和存储位置很关键
undo log 必须在数据变更写入 Buffer Pool 之前就落盘(或至少写入 undo log buffer),这是原子性的底线。否则事务失败时,连“撤回依据”都没了。
它不单独存在一个文件里,而是存于 InnoDB 的表空间中:
- 默认在共享表空间
ibdata1里,由UNDO_LOG_SEGMENT管理 - MySQL 5.6+ 支持
innodb_undo_tablespaces拆成多个独立 undo 表空间文件(如undo001、undo002) - MySQL 8.0 默认开启
innodb_undo_log_truncate,允许在事务提交且无活跃快照后自动截断过期 undo 日志
注意:undo log 不是写完就扔,它要等到所有依赖它的事务(包括正在做 MVCC 读的事务)都结束,才由 purge 线程真正清理。
rollback 执行时,undo log 是怎么被读取和应用的?
InnoDB 回滚不是逐条 SQL 重放,而是按 undo log 记录的物理顺序(LIFO,后进先出)倒着解析并执行逆操作。因为最后做的修改最可能依赖前面的状态,倒序能保证中间状态一致。
举个例子:
/* 事务内依次执行 */ INSERT INTO t1 VALUES (70, 701); UPDATE t1 SET i1 = 702 WHERE id = 70; DELETE FROM t1 WHERE id = 70;
对应生成的 undo log 链是:[delete-log] → [update-log] → [insert-log]。回滚时先处理 delete-log(恢复该行),再 update-log(还原字段),最后 insert-log(删掉插入的行)。
这个过程由 InnoDB 内核在 trx_rollback_for_mysql() 中驱动,全程不走 SQL 解析器,直接操作记录结构,所以比手动写 ROLLBACK 语句快得多。
为什么有时候 rollback 很慢?常见卡点在哪
回滚慢,往往不是 SQL 复杂,而是 undo log 链太长或 purge 延迟导致的资源竞争:
- 大事务(比如单次更新百万行)会产生海量
undo log,回滚时要逐条解析 + 应用,I/O 和 CPU 压力双高 - 如果系统里有长事务(比如未提交的只读事务)持续持有快照,purge 线程无法清理旧
undo log,表空间膨胀,回滚时要扫描更多日志 -
innodb_force_recovery > 0时,undo 机制可能被禁用,rollback 会直接报错ERROR 1752 (HY000): Unable to read from the undo log - DDL 操作(如
ALTER TABLE)期间若发生中断,其内部事务的 undo log 可能残留,影响后续回滚行为
真正容易被忽略的是:ROLLBACK 本身不写 binlog,但事务中已生成的 binlog 缓存在 trx cache 中并不会被清空 —— 这意味着如果误用 mysqlbinlog 解析并重放,可能重复执行已回滚的操作。


















