mysql两阶段提交是专为协调redo log与binlog原子性而设计的本地机制,非通用分布式协议;其核心依赖innodb_flush_log_at_trx_commit=1和sync_binlog=1双开启,通过prepare(redo log落盘并标记xid)与commit(binlog落盘后更新redo log状态)两阶段严格顺序执行,并在崩溃恢复时基于xid交叉校验实现一致性。

MySQL 的两阶段提交(2PC)不是为分布式事务设计的通用协议,而是专为协调 redo log 和 binlog 两个独立日志系统而定制的原子性机制。它不靠网络通信或外部协调者,而是由 InnoDB 引擎和 Server 层在本地协同完成——只要 innodb_flush_log_at_trx_commit=1 和 sync_binlog=1 同时生效,就能堵住绝大多数一致性漏洞。
prepare 阶段必须写盘且标记状态
事务执行完后,进入提交流程的第一步不是直接落盘 binlog,而是先让 InnoDB 把变更写入 redo log 缓冲区,并调用 fsync 刷盘,再将该事务在 redo log 中标记为 TRX_STATE_PREPARED(即 prepare 状态)。这一步的关键在于:
-
redo log必须真正落盘(依赖innodb_flush_log_at_trx_commit=1),否则崩溃后连 prepare 记录都不存在,无法判断该事务是否该回滚 - 此时事务尚未释放行锁、未更新 MVCC 可见性,但已占用
undo log资源 -
binlog完全没动,所以即使此刻崩溃,从库不会收到这条日志,主库恢复时也会按“无对应 binlog”回滚该 prepared 事务
commit 阶段严格依赖 binlog 写成功
只有 redo log prepare 成功返回后,Server 层才开始把事务的逻辑 SQL 写入 binlog 文件缓冲区,再调用 fsync 刷盘。只有 binlog 成功落盘,才会通知 InnoDB 执行最终 commit——也就是把 redo log 中对应事务的状态从 prepared 改为 committed。
- 如果
binlog写失败(比如磁盘满、权限不足),整个事务回滚,redo log中的 prepare 记录会被清理 - 如果
binlog写成功,但 MySQL 在发 commit 指令前 crash,重启时会扫描binlog最后一个XID,然后去redo log中查找匹配的 prepared 事务并补 commit - 顺序绝不能颠倒:先
binlog后redo log commit是唯一安全路径;反过来就失去保护意义
崩溃恢复时靠 XID 关联双日志
MySQL 启动时的 crash recovery 不是凭空猜测,而是基于 redo log 和 binlog 的交叉校验。核心判断逻辑只看三件事:
- 若
redo log中事务有commit标记 → 直接提交 - 若只有
prepared标记 → 查binlog是否存在完整对应的XID记录 - 存在且完整 → 补
commit;不存在或损坏 → 回滚
这个过程依赖 binlog 和 redo log 都记录了同一个全局事务 ID(XID),它是两者能关联起来的唯一纽带。没有 XID,恢复逻辑就断了。
容易被忽略的配置与副作用
线上出问题往往不是协议本身失效,而是配置或使用方式破坏了前提条件:
-
innodb_support_xa在 5.7.9+ 默认开启,但若被手动设为OFF,MySQL 会跳过 prepare 阶段,退化为单阶段提交,彻底失去双日志保护 - 长时间未完成的 prepared 事务(比如应用卡在 commit 前)会拖慢
mysqldump --single-transaction的快照创建,甚至阻塞FLUSH TABLES WITH READ LOCK -
binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count会影响binlog刷盘时机,但只要sync_binlog=1生效,组提交内部仍保证每个事务的binlog都 fsync 过
真正要盯紧的,永远是那两个开关:innodb_flush_log_at_trx_commit=1 和 sync_binlog=1。少一个,两阶段提交就形同虚设。











