mysql崩溃重启后通过xid匹配redo log的prepared事务与binlog:若binlog中存在对应xid的完整事件则提交,否则回滚;该机制依赖innodb_support_xa=on、sync_binlog=1和innodb_flush_log_at_trx_commit=1协同保障不丢不重。

MySQL事务原子性在崩溃重启后不丢不重,靠的是InnoDB的undo log + redo log + 两阶段提交(2PC)协同作用,不是单靠某一个日志。
崩溃时事务处于prepare阶段,怎么判断该提交还是回滚?
MySQL重启后扫描redo log,遇到只有prepare标记、没有commit标记的事务,会用该事务的XID去查binlog:
- 如果对应
XID的binlog完整存在(含COMMIT或XIDevent),就提交这个事务 - 如果binlog缺失或不完整,就回滚这个事务
这个判断逻辑由innodb_support_xa=ON(默认开启)和sync_binlog=1配合支撑。若sync_binlog=0,binlog可能只写到page cache未落盘,崩溃后binlog丢失,就会误判为“该回滚”,导致已提交事务丢失——这就是不丢不重的关键断点。
undo log为什么不会在崩溃中丢失?
undo log本身也是InnoDB管理的数据页,它的写入同样走WAL(Write-Ahead Logging)路径:
- 修改undo页前,先写对应的redo log(确保undo操作可恢复)
- 事务提交前,必须保证该事务涉及的undo log已刷盘(否则回滚信息不可用)
- 崩溃重启后,InnoDB先用redo log恢复所有已提交/已prepare的页状态,再根据上述XID规则决定是否清理undo log
注意:innodb_undo_log_truncate=ON(默认)允许后台线程定期截断已不再需要的undo log,但活跃事务或未完成purge的undo段绝不会被提前清理。
为什么不能只靠redo log或只靠binlog?
单独依赖任一者都会破坏原子性边界:
- 只靠
redo log:它只记录“数据页改成了什么样”,无法区分“这个修改属于哪个事务”或“该不该提交”。崩溃后可能把未提交事务的脏页也重做了 - 只靠
binlog:它是逻辑日志、服务层日志,不包含页级物理变更,也无法支撑崩溃后的页恢复;且binlog不记录回滚所需的老值,无法实现原子性回退 - 两阶段提交正是为了对齐二者边界:prepare阶段写redo log并标记XID,commit阶段才同时写binlog和redo log的commit标记
真正起效的前提是innodb_flush_log_at_trx_commit=1(强制每次commit都fsync redo log)与sync_binlog=1同时生效。任意一个设为0,就可能出现“主库说提交了,但从库没收到binlog”或“崩溃后redo log里多出半截事务”的情况。
最容易被忽略的是XID的全程绑定——从事务开始、prepare、binlog写入到redo log commit,所有环节都必须携带同一个XID。一旦中间环节XID不一致(比如某些自定义XA事务手动指定错误),崩溃恢复时就会找不到匹配项,直接回滚,造成数据丢失。











