先说结论:MySQL的COMMIT并非一次性完成的操作,而是InnoDB与Server层之间的一场“双人交接”。你看到的“提交成功”,实际上是协调者——即InnoDB事务管理模块,确认了redo log与binlog均已按顺序完成落盘、且状态一致后的结果。整个过程分为两个阶段,这也是数据库领域经典的“两阶段提交”协议(2PC)。
prepare阶段:先让redo log进入可恢复状态
执行COMMIT后,InnoDB会立即进入prepare阶段。具体流程如下:
redo log先写入缓冲区,随后调用fsync进行刷盘——这一行为由innodb_flush_log_at_trx_commit=1控制。刷盘时,redo log的状态会被标记为PREPARE,同时写入一个事务唯一标识:XID。- 请注意,此时数据尚未对外可见,锁也未释放,
undo log更不会清理。换言之,事务仍处于“半挂起”状态。 - 关键点在于:如果此刻系统崩溃,InnoDB重启后会扫描
redo log,发现存在PREPARE状态的事务,但它不会直接提交——它需要等待binlog的“判决”。
commit阶段:binlog成为最终裁决者
Server层收到InnoDB的prepare成功反馈后,才开始写入binlog。流程如下:
binlog追加写入文件,并调用fsync(受sync_binlog=1控制)。这条binlog记录必须关联同一个XID。- 如果
binlog写入失败——例如磁盘已满或权限错误——Server层会通知InnoDB执行rollback,该事务将彻底作废。 - 如果
binlog写入成功,Server层就会向InnoDB发送commit指令。InnoDB收到后,会将redo log中对应该XID记录的状态更新为COMMIT——注意,这一步不强制刷盘,只需写入OS page cache。 - 随后,释放锁、清理
undo log,事务才算真正结束。

崩溃恢复时,MySQL如何“读心”?
MySQL重启时并不会“猜测”某个事务的状态,而是严格比对两个日志中的XID:
- 如果
redo log里有XID标记为PREPARE,但binlog中查不到该XID——说明binlog写入失败,系统会认定该事务需要回滚,并自动执行rollback。 - 如果
binlog中有该XID,但redo log中无对应PREPARE记录——这种情况理论上不会发生,因为binlog写入前必须经过prepare阶段。如果出现,说明流程被绕过(例如禁用了innodb_support_xa),这将直接导致主从不一致。 - 只有两者都存在且匹配,系统才会完成最终提交。这就是
binlog在恢复过程中充当“裁判”角色的原因。
容易被忽略的配置陷阱
两阶段提交能否真正发挥作用,高度依赖两个参数的实际值。以下是几个常见陷阱:
innodb_flush_log_at_trx_commit=0或2:prepare阶段的redo log可能未刷盘,崩溃后连PREPARE状态都丢失,但binlog却已存在。这种情况会导致主从不一致。sync_binlog=0:binlog仅写入文件系统缓存,崩溃后丢失。InnoDB会因找不到XID而回滚事务,但业务端可能已经收到“提交成功”的响应。innodb_support_xa=OFF(MySQL 5.7.7之后默认ON):若禁用XA协议支持,将跳过prepare阶段,直接退化为单阶段提交,彻底破坏2PC机制。
简而言之,真正起作用的并非COMMIT语句本身,而是背后那套靠XID对齐、靠fsync顺序、靠崩溃后交叉校验来维持的协作逻辑。一旦任何一个环节脱离这个链条,一致性将不再有任何保障。
