mysql事务提交成功不等于数据落盘,关键取决于flush和sync子阶段是否完成:若innodb_flush_log_at_trx_commit=1但crash发生在flush前,或磁盘写缓存未禁用,redo可能未真正落盘;sync_binlog=0时binlog滞留page cache,断电即丢失。

MySQL 事务提交成功,不等于数据已落盘;真正决定“是否丢数据”的关键点,在于 commit 阶段中 flush 和 sync 子阶段的刷盘行为是否完成。
为什么 COMMIT 返回成功后,机器断电仍可能丢数据?
因为 MySQL 的 COMMIT 返回只是表示事务已进入 TRX_STATE_COMMITTED_IN_MEMORY 状态,InnoDB 已释放锁、更新事务状态、把 update undo 加入 history list,但 redo 和 binlog 可能还卡在操作系统的 page cache 里。
- 如果
sync_binlog = 0,binlog 写入后不触发fsync(),断电时 page cache 中的 binlog 就丢了 - 如果
innodb_flush_log_at_trx_commit = 1,redo 日志会在flush子阶段强制刷盘,但若此时 crash 发生在flush完成前,redo 也可能未落盘 - 更隐蔽的是:即使
innodb_flush_log_at_trx_commit = 1,若磁盘本身开了写缓存(且未禁用),操作系统认为刷盘成功,实际数据仍在磁盘缓存中
Prepare 阶段到底做了什么?
Prepare 是二阶段提交的第一步,它不刷盘,只做内存和日志结构层面的标记:
- 将事务状态设为
TRX_STATE_PREPARED - 写入 prepare 标记到 redo 日志(内容包含 XID)
- 修改 insert/update undo 段头页的状态字段(如
TRX_UNDO_CACHED或TRX_UNDO_TO_PURGE) - 不写 binlog;也不释放行锁或表锁;事务仍可被回滚
注意:PREPARE 不是 SQL 语句里的 PREPARE stmt FROM ...,而是内核级事务状态切换动作,用户不可见。
Commit 阶段三个子阶段各干了啥?
真正的持久化保障全落在 commit 阶段的三个子阶段上,顺序不可逆:
-
flush子阶段:触发fsync()把所有待提交事务的 redo 日志刷盘;同时把它们的 binlog 写入 binlog 文件(仅write(),不一定落盘) -
sync子阶段:根据sync_binlog值决定是否调用fsync()强制刷 binlog 到磁盘。若sync_binlog = 1,这里才真正保证 binlog 持久化 -
commit子阶段:更新事务状态为TRX_STATE_COMMITTED_IN_MEMORY;释放所有锁;重置事务对象;把 update undo 加入history list,供 purge 线程后续清理
这三步中,flush 和 sync 是 IO 密集型操作,也是组提交(group commit)优化的核心对象;而 commit 子阶段纯内存操作,极快。
最容易被忽略的配置组合风险
很多线上事故源于对两个参数协同作用的理解偏差:
-
innodb_flush_log_at_trx_commit = 1+sync_binlog = 0:redo 落盘了,但 binlog 还在 page cache,crash 后 recovery 无法通过 binlog 找到 XID,事务会被回滚 → 主从数据不一致 -
innodb_flush_log_at_trx_commit = 2+sync_binlog = 1:redo 只写入 OS cache,binlog 强制落盘,此时若 crash,redo 丢失但 binlog 存在,recover 会尝试提交,但 InnoDB 因无对应 redo 而失败 → 数据库启动失败或报错innodb: Database page corruption - 安全底线组合只有两个:
innodb_flush_log_at_trx_commit = 1且sync_binlog = 1,或启用binlog_group_commit_sync_delay微调延迟以平衡性能
真正要命的不是单个参数值,而是 redo 与 binlog 在崩溃时的可见性是否对齐 —— 这才是两阶段提交存在的根本原因。











