一、为什么需要 binlog 和 redo log 的两阶段提交?
场景设定
- 执行一条SQL
UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT; - MySQL 架构分层:
- Server 层:负责 SQL 解析、binlog 记录;
- InnoDB 引擎层:负责数据存储、redo/undo log、事务控制。
矛盾点
| 日志类型 | 所属层 | 用途 | 是否参与崩溃恢复 |
|---|---|---|---|
| Redo Log | InnoDB | 崩溃恢复时重做已提交事务 | ✅ 是 |
| Binlog | Server | 主从复制、PITR 恢复 | ❌ 否(InnoDB 不感知) |
关键问题 :如果只靠 InnoDB 自己,它不知道 binlog 是否写成功;
如果只靠 binlog,它不知道 InnoDB 是否持久化。
两者脱节 → 主从数据不一致!
经典反例(无 2PC):
- 先写 redo log 成功 → 事务在主库"存在";
- 写 binlog 前 crash → 从库无此事务;
- 主库重启后数据保留,从库缺失 → 永久性主从不一致。
✅ 解决方案 :引入 内部两阶段提交(Internal 2PC),强制两个日志的写入具有原子性
二、两阶段提交的核心目标
✅ 保证 redo log 与 binlog 的逻辑一致性,使得:
- 崩溃恢复后,主库数据状态 = binlog 记录的状态;
- 从库通过 binlog 复制,能与主库保持完全一致;
- 支持基于 binlog 的 PITR(Point-in-Time Recovery)。
三、两阶段提交的具体流程(InnoDB + Binlog)
假设事务 T 执行 COMMIT,MySQL 按以下步骤执行:
阶段 1:Prepare 阶段(存储引擎层)
- InnoDB 将事务的 redo log 写入日志缓冲区;
- 调用
fsync()将 redo log 持久化到磁盘; - 将事务状态标记为 PREPARED(但未释放锁,未真正提交);
- 返回成功给 Server 层。
此时事务"半提交",等待 binlog 写入确认。
阶段 2:Commit 阶段(Server 层 + 存储引擎层)
- Server 层将事务的 binlog 写入 binlog 缓冲区;
- 根据
sync_binlog参数决定是否调用fsync()刷盘; - 若 binlog 写入成功,Server 层通知 InnoDB 正式提交;
- InnoDB 将事务状态改为 COMMITTED,释放行锁/间隙锁等资源。
只有当 redo log(prepare) + binlog(commit)都持久化成功,事务才算真正完成。
四、崩溃恢复时如何处理?
MySQL 启动时,会执行 崩溃恢复(Crash Recovery),关键步骤如下:
- InnoDB 从 redo log 中扫描所有处于 PREPARED 状态的事务;
- 对每个 PREPARED 事务,去 binlog 中查找是否存在对应记录 ;
- ✅ 如果存在 → 说明 binlog 已写入,应 提交 该事务;
- ❌ 如果不存在 → 说明 binlog 未写入,应 回滚 该事务;
- 完成恢复,保证数据与 binlog 一致。
这就是 "用 binlog 作为 redo log 提交的最终依据" 的核心思想。
五、关键参数配置(影响 2PC 安全性)
| 参数 | 默认值(MySQL 8.0) | 作用 | 推荐值(强一致性) |
|---|---|---|---|
innodb_flush_log_at_trx_commit |
1 | 控制 redo log 刷盘策略 | 1(每次事务 commit 都 fsync) |
sync_binlog |
1 | 控制 binlog 刷盘频率 | 1(每次事务都 sync binlog) |
只有当两个参数都为 1 时,才能实现真正的 Crash-Safe 主从一致性。
六、图解:binlog 与 redo log 的 2PC 流程

MySQL 两阶段提交流程详解
| 步骤 | 交互方向 | 操作描述 | 技术含义与背景说明 |
|---|---|---|---|
| 1 | Client → MySQL Server | COMMIT |
客户端发起事务提交请求,触发整个提交流程。此时事务在内存中已完成逻辑修改,但尚未持久化。 |
| 2 | MySQL Server → InnoDB | Prepare Transaction | Server 层通知 InnoDB 进入 Prepare 阶段(2PC 第一阶段),要求存储引擎准备提交。 |
| 3 | InnoDB → Redo Log (Disk) | Write & fsync redo log (state=PREPARED) | InnoDB 将事务的 redo log 写入日志文件,并调用 fsync() 强制刷盘,事务状态标记为 PREPARED。这是崩溃恢复的关键依据。 |
| 4 | Redo Log (Disk) → InnoDB | OK | redo log 成功落盘,InnoDB 确认 prepare 阶段完成。事务处于"半提交"状态,锁未释放。 |
| 5 | InnoDB → MySQL Server | Prepare OK | InnoDB 告知 Server 层:已准备好,可继续写 binlog。这是 2PC 中参与者的"投票同意"。 |
| 6 | MySQL Server → Binlog (Disk) | Write binlog | Server 层将事务的 binlog 记录写入 binlog 缓冲区。若使用 row 格式,会记录完整的行变更。 |
| 7 | Binlog (Disk) → MySQL Server | sync_binlog=1 → fsync | 若 sync_binlog=1,则立即调用 fsync() 将 binlog 刷入磁盘,确保主从复制和 PITR 可靠性。 |
| 8 | Binlog (Disk) → MySQL Server | Binlog OK | binlog 成功写入(并刷盘),Server 层确认第二阶段前提满足。 |
| 9 | MySQL Server → InnoDB | Commit Transaction | Server 通知 InnoDB 正式提交事务(2PC 第二阶段)。 |
| 10 | InnoDB → Redo Log (Disk) | Update state to COMMITTED | InnoDB 将事务状态从 PREPARED 改为 COMMITTED,释放锁,事务对其他会话可见。此步可能不立即刷盘(因数据页后续由后台线程刷入)。 |
| 11 | InnoDB → Client | Transaction Complete | 事务完成,Server 返回成功响应给客户端,整个流程结束。 |
关键保障机制总结
- 原子性保障:通过 2PC 确保 redo log 与 binlog 要么都成功,要么都失败。
- 崩溃恢复逻辑 :
- 启动时扫描 redo log 中所有
PREPARED事务; - 对每个事务,检查 binlog 是否存在;
- 存在 → 提交;
- 不存在 → 回滚。
- 启动时扫描 redo log 中所有
- 强一致性依赖参数 :
innodb_flush_log_at_trx_commit = 1(redo log 每次 commit 都刷盘)sync_binlog = 1(binlog 每次事务都刷盘)
七、总结汇总表
| 项目 | 说明 |
|---|---|
| 目的 | 保证 redo log 与 binlog 的原子性,防止主从不一致 |
| 触发场景 | 开启 binlog 且使用支持事务的引擎(如 InnoDB) |
| 两阶段 | 1. Prepare(InnoDB 写 redo log)2. Commit(Server 写 binlog + InnoDB 提交) |
| 崩溃恢复 | 通过比对 PREPARED 事务与 binlog 决定提交/回滚 |
| 关键参数 | innodb_flush_log_at_trx_commit=1 + sync_binlog=1 |
| 性能代价 | 两次 fsync(redo + binlog),I/O 开销大 |
| 安全级别 | 满足 ACID 中的 A(原子性)和 D(持久性),支撑强一致性复制 |
八、面试延伸问题(附答案)
问题 1:如果 MySQL 在写完 redo log(prepare 状态)后 crash,但 binlog 还没写,重启后这个事务会被提交还是回滚?为什么?
参考答案 :
会被 回滚。
原因:
MySQL 启动时,InnoDB 会扫描 redo log,找到所有状态为 PREPARED 的事务。然后,它会去检查 binlog 文件中是否存在该事务的记录:
- 如果 binlog 中没有,说明事务在 prepare 后、写 binlog 前崩溃,此时 binlog 未持久化,从库也无法获取该事务;
- 为保证主从一致,必须 回滚 该事务,避免主库有数据而从库没有。
这正是 2PC + 崩溃恢复机制的核心价值。
问题 2:为什么不能先写 binlog 再写 redo log?顺序能否颠倒?
参考答案 :
不能颠倒。原因如下:
假设先写 binlog,再写 redo log:
- 如果写完 binlog 后 crash,redo log 未写入;
- 重启后,InnoDB 崩溃恢复时 找不到该事务的 redo log,会认为事务未发生;
- 但从库已经通过 binlog 应用了该事务 → 主从不一致!
而当前顺序(先 prepare redo log,再写 binlog)保证了:
- 只有 redo log 和 binlog 都成功,事务才生效;
- 若中间 crash,可通过 redo log 中的 PREPARED 状态 + binlog 存在性来安全决策。
因此,先 redo(prepare),后 binlog,再 redo(commit) 是唯一安全的顺序。