MySQL通过两阶段提交(prepare→binlog fsync→commit)和XID交叉验证保证binlog与redo log一致:仅当二者均落盘且XID匹配时才提交,否则回滚,避免主从不一致或崩溃恢复失败。

事务提交后 binlog 写成功但 redo log 未落盘,数据会不一致
这种情况会导致主从数据错乱、崩溃恢复失败,是 MySQL 两阶段提交(2PC)必须规避的致命风险。根本原因在于:binlog 是 Server 层逻辑日志,redo log 是 InnoDB 引擎层物理日志,二者职责不同、写入时机不同、持久化保障级别也不同。
典型错误现象包括:
- 主库宕机重启后,某条已“提交”的更新在表中查不到(脏页没刷、redo 又丢了),但从库却通过 binlog 执行了该更新 → 主从数据不一致
- 用 binlog 恢复到某个时间点后,部分事务“有日志无数据”,恢复出的库无法通过一致性校验
- MySQL 启动时执行 crash recovery,发现某些事务在 redo log 中缺失 prepare 记录,无法判断是否该重放 → recovery 中断或跳过
innodb_flush_log_at_trx_commit ≠ 1 是直接诱因
当 innodb_flush_log_at_trx_commit 设为 0 或 2 时,事务提交只保证 redo log 进入 OS cache,不强制 fsync 到磁盘。此时若主机断电或 mysqld 进程异常退出,OS cache 中的 redo log 就丢失了。
关键点在于:binlog 的刷盘由 sync_binlog 控制,而 redo log 的刷盘由 innodb_flush_log_at_trx_commit 控制 —— 二者完全独立。即使 sync_binlog=1,只要 innodb_flush_log_at_trx_commit≠1,就可能出现 binlog 已落盘、redo log 仍在内存/OS cache 的窗口期。
常见配置组合影响:
-
innodb_flush_log_at_trx_commit=0+sync_binlog=1:最危险。事务提交几乎不落盘 redo,崩溃后大概率丢数据,但 binlog 里全有 → 从库多、主库少 -
innodb_flush_log_at_trx_commit=2+sync_binlog=1:稍好,至少 redo log 进了 OS cache,但若系统级崩溃(如掉电),OS cache 仍可能丢失 → 仍有不一致风险 -
innodb_flush_log_at_trx_commit=1+sync_binlog=1:唯一能严格保证崩溃安全的组合。两份日志都经fsync落盘,2PC 才真正闭环
MySQL 用 prepare/commit 状态标记兜底
InnoDB 在写 redo log 时,并非直接写 commit,而是先写 prepare 状态;等 binlog 写完并 fsync 成功后,再写 redo log 的 commit 标记。这个状态分离是 crash recovery 的判断依据。
如果崩溃发生在 binlog 写完但 redo log 还没写 commit 时,MySQL 启动 recovery 阶段会扫描 redo log:
- 发现某事务只有
prepare无commit→ 查 binlog:若该 XID 存在,则补写 commit 并重放;若不存在,则回滚该事务 - 但如果 redo log 的
prepare记录本身就没落盘(比如innodb_flush_log_at_trx_commit=0),那 recovery 根本看不到这个事务 → 直接当作从未发生过
也就是说,prepare 记录不落盘,整个事务对 InnoDB 来说就是“不存在”的,哪怕 binlog 里明明白白记着它。
实际部署中容易被忽略的细节
很多人以为开了 sync_binlog=1 就万事大吉,却忽略了 innodb_flush_log_at_trx_commit 的默认值其实是 1 —— 但有些云厂商 RDS 默认改成了 2,有些 DBA 为了压测吞吐手动调成 0,线上跑了一年都没事,直到某次意外断电才暴露问题。
另一个隐性坑是文件系统缓存:即使两个参数都设为 1,若挂载 MySQL 数据目录的磁盘用了 data=writeback(如 ext4 默认),或者 RAID 卡禁用了 write-back cache 但没配电池/电容,fsync 仍可能不真正落盘。这类底层设施问题,光看 MySQL 配置是发现不了的。


















