根本原因是防止崩溃后redo log与binlog逻辑断层导致主从不一致和时间点恢复错乱;update的两阶段在commit时触发:innodb先写redo log并标记prepared,server层确认后fsync binlog,再通知innodb改为committed。

MySQL 执行 UPDATE 语句时需要两阶段提交,根本原因不是“为了流程规范”,而是防止崩溃后 redo log 和 binlog 出现逻辑断层——一旦断层,主从数据必然不一致,时间点恢复必然错乱。
不走两阶段提交,UPDATE 崩溃后会立刻丢一致性
假设一条 UPDATE t_user SET name='xiaolin' WHERE id=1 正在提交:
- 如果先写完
redo log(标记已修改),再写binlog时进程崩溃 → 主库重启后数据是'xiaolin',但binlog里没这条记录 → 从库永远同步不到,主从分裂 - 如果先写完
binlog,再写redo log的commit标记前崩溃 → 主库重启后事务回滚(name还是旧值),但从库重放binlog后变成'xiaolin'→ 从库比主库多一条变更,更危险
这两种“半写成功”状态,在单日志顺序刷盘下无法避免。两阶段提交不是增加复杂度,而是用 prepare + commit 的状态锚点,把“是否可恢复”和“是否可复制”绑死在同一个判断条件上。
UPDATE 的两阶段实际发生在哪几个关键点?
以显式事务为例(BEGIN; UPDATE ...; COMMIT;),真正触发两阶段的是 COMMIT 这一动作,而非 UPDATE 本身。整个链路中,这几个位置不可跳过:
-
InnoDB在COMMIT开始时,把redo log写入磁盘并标记为PREPARED状态(此时事务未释放锁、未真正生效) - Server 层确认
PREPARED成功后,才把该事务的binlog写入文件并调用fsync - 只有
binlogfsync成功,Server 层才通知InnoDB把对应redo log改为COMMITTED
注意:UPDATE 过程中生成的 undo log 和脏页修改,都发生在 prepare 阶段之前;两阶段只管“提交那一刻”的日志原子性。
哪些配置会让两阶段提交形同虚设?
两阶段提交机制本身由 MySQL 内置实现,但它的可靠性完全依赖底层刷盘是否真实落盘。以下两个参数若非 1,prepare 阶段就可能白做:
-
innodb_flush_log_at_trx_commit = 0或2:redo log可能只写到内存或 OS 缓存,崩溃即丢 —— 即使标记了PREPARED,恢复时也找不到它 -
sync_binlog = 0或其他非1值:binlog写入后不强制fsync,崩溃时binlog文件里看似有内容,实则还在 page cache 中,恢复时检查失败 →PREPARED事务被回滚
这两个参数必须同时为 1,两阶段提交才能真正堵住日志脱节的漏洞。很多线上环境为性能调低它们,却忘了代价是放弃原子性保障。
真正容易被忽略的,不是“要不要两阶段”,而是崩溃恢复时 InnoDB 并不信任自己的 PREPARED 记录 —— 它每次启动都会扫描所有 PREPARED 事务,然后逐个去磁盘翻 binlog 文件找对应 XID。如果 binlog 路径配置错误、权限不足、或被运维误删,哪怕 sync_binlog = 1,恢复也会失败。











