事务回滚后数据没变但磁盘写了两次,因innodb先写redo log(物理日志,用于崩溃恢复)和undo log(逻辑日志,用于回滚与mvcc),二者分工明确、不可替代。

为什么事务回滚后数据没变,但磁盘文件却写了两次?
因为 InnoDB 不直接改数据页,而是先写 redo log(重做日志)和 undo log(回滚日志)。redo log 保证崩溃后能重放操作,undo log 保证事务失败时能逆向恢复——两者分工明确,不能互相替代。
常见错误现象:UPDATE 执行完立刻查不到新值,或事务中断后发现某条记录“凭空多了一行”。这往往不是日志失效,而是你查的是别的事务隔离级别下的快照(比如 READ COMMITTED 下查不到未提交的变更),或者误删了 undo log 对应的段。
-
redo log是物理日志,记录“某个页的某个偏移位置写入了什么字节”,只用于崩溃恢复,不参与事务回滚 -
undo log是逻辑日志,记录“这条 INSERT 对应的 DELETE 语句”或“UPDATE 前的旧值”,只用于回滚和 MVCC 快照读 - 每次事务修改数据,InnoDB 都会先分配
undo log空间、写入旧值,再修改内存中的数据页,最后刷redo log到磁盘(按innodb_flush_log_at_trx_commit配置)
崩溃后 MySQL 怎么靠 redo log 恢复到一致状态?
MySQL 启动时会检查 redo log 文件头的 checkpoint 位置,从那里开始扫描日志,逐条重放已写入但尚未刷盘的数据页变更。这个过程叫 crash recovery,它不依赖 binlog,也不读表空间文件原始状态。
关键点在于:只有已写入 redo log 的事务才会被重放;而是否“已提交”由日志中的 XID 和 commit mark 共同决定。如果日志里只有 prepare 没有 commit,InnoDB 会去 binlog 查这个 XID 是否存在(启用 innodb_support_xa=ON 且 sync_binlog=1 时),来决定是提交还是回滚。
- 若
innodb_flush_log_at_trx_commit=0,事务提交时只写日志到 OS cache,崩溃可能丢失最近 1 秒事务 - 若
=1(默认),每次COMMIT都fsync日志文件,最安全但有性能损耗 - 若
=2,只fsync到磁盘一次/秒,崩溃最多丢 1 秒事务,但比 0 更可靠
undo log 被清理掉之后,老事务还能读到快照吗?
不能。MVCC 依赖活跃事务中最老的那个 read view 来判断哪些版本可见。只要还有事务在用某个 undo log 记录的版本,InnoDB 就不会清理它。一旦所有依赖它的事务结束,Purge 线程才会异步删除这些 undo log 页面。
典型踩坑场景:SELECT 查询跑太久(比如没加索引导致全表扫描),同时大量 DML 持续写入,导致 undo log 空间暴涨甚至填满 ibdata1。这时你会看到错误:ERROR 1206 (HY000): The total number of locks exceeds the lock table size,本质是 undo 空间耗尽,无法分配新回滚段。
- 监控方法:查
information_schema.INNODB_TRX表看TRX_STARTED时间,揪出长事务 - 配置项
innodb_max_purge_lag可以让写入线程在 purge 跟不上时主动减速,但治标不治本 -
innodb_undo_tablespaces设为大于 0 可把 undo 存进独立表空间,避免 ibdata1 无限膨胀
binlog 和 redo log 一起用,为什么还要两套日志?
因为它们服务的目标完全不同:redo log 是 InnoDB 引擎层的崩溃恢复机制,只管“单机本地数据页怎么不丢”;binlog 是 Server 层的逻辑日志,用来做主从复制、基于时间点的恢复(PITR)、审计等跨引擎通用功能。
二者必须保持一致才能保证主从数据一致。MySQL 用 XA 协议协调:事务提交前先写 redo log prepare,再写 binlog,最后写 redo log commit。Crash 发生在这三步之间时,恢复阶段靠两日志交叉验证补全状态。
- 如果只开
binlog关redo log(不可能,InnoDB 强制启用),崩溃后连本地数据都恢复不了 - 如果只开
redo log关binlog,就没法做主从,也没法做 PITR - 线上必须同时开启,并确保
sync_binlog=1+innodb_flush_log_at_trx_commit=1,否则主从延迟或丢数据风险极高
undo log 占满磁盘、redo log 切换卡住、binlog 和 redo 状态不一致时,你得能从 SHOW ENGINE INNODB STATUS 里快速定位到哪一行在阻塞 purge,哪条事务没写完 commit 标记。











