MySQL事务提交采用两阶段提交:1. InnoDB将redo log写入并标记为prepare(含XID);2. Server层写入binlog;3. InnoDB再标记redo log为commit;崩溃恢复时依据XID比对binlog决定提交或回滚。

事务提交时,redo log 必须先 prepare、再 commit,且必须与 binlog 协同完成——否则主备不一致或崩溃后数据丢失。
redo log 的 prepare 和 commit 两阶段怎么触发
MySQL 的事务提交不是原子操作,而是由执行器(Server 层)和引擎(InnoDB 层)协作完成的。关键点在于:InnoDB 只负责 redo log 的 prepare 和 commit 状态标记,真正决定是否落盘,取决于 innodb_flush_log_at_trx_commit 配置。
- 执行器调用
innodb_commit接口前,InnoDB 已把修改记录写入redo log buffer,并标记为prepare状态,同时带上 XID - 执行器写完
binlog后,才再次调用 InnoDB 的提交接口,InnoDB 将对应 XID 的 redo log 标记为commit状态 - 此时是否刷盘,不看“commit”这个状态本身,而看
innodb_flush_log_at_trx_commit的值:设为 1 时,commit调用会触发fsync;设为 2 时只write到 page cache;设为 0 则完全不刷
为什么未提交的事务 redo log 也可能落盘
redo log buffer 是共享内存区域,多个事务日志共存其中。即使某个事务还没提交,它的日志也有可能被其他事务的提交行为“捎带”刷盘。
- 当事务 A 提交且
innodb_flush_log_at_trx_commit = 1,InnoDB 会把整个redo log buffer中所有日志(包括事务 B 的 prepare 日志)一并fsync到磁盘 - 后台线程每秒轮询一次,主动把
redo log buffer中的内容write+fsync到磁盘,不管里面有没有未提交事务 - 当
redo log buffer使用量超过innodb_log_buffer_size / 2,后台线程也会提前触发写盘
innodb_flush_log_at_trx_commit=1 不等于“每次只刷当前事务”
这是最容易误解的一点:设为 1 确保了“每个事务提交都强制持久化”,但持久化的不是单条日志,而是整个 buffer 中已累积的所有日志。
- 它不区分事务边界,只是把 buffer 当前全部内容刷下去
- 因此高并发下,多个小事务提交可能被合并成一次
fsync,反而比单个大事务更高效 - 但若 buffer 太小(如
innodb_log_buffer_size仅 1MB),频繁刷盘会导致 IO 毛刺;建议生产环境设为 4–16MB - 注意:buffer 大小不影响日志正确性,只影响刷盘频率和系统抖动
崩溃恢复时 redo log 的状态判断逻辑
MySQL 重启时,并不依赖 redo log 的“commit”标记位来决定是否重放,而是靠 XID 与 binlog 对齐。
- 扫描 redo log,找出所有 XID 不为空的
prepare状态事务 - 检查这些 XID 是否完整存在于 binlog 中(即 binlog 文件里有对应 event 且 checksum 正确)
- 存在 → 提交该事务;不存在 → 回滚该事务
- 所以即使 redo log 中某条日志已
fsync到磁盘但仍是prepare状态,只要 binlog 缺失,它就一定被回滚
真正复杂的地方在于:prepare/commit 状态只是逻辑标记,刷盘时机由参数控制,而恢复逻辑又绕过这些标记、直接比对 XID。这三层解耦让 MySQL 在性能、安全、一致性之间取得平衡,但也意味着不能只盯着一个参数或一个状态做判断。


















