本文由《MySQL 内部两阶段提交机制深度解析》与《MySQL 内部两阶段提交(2PC)深度解析》合并整理,去重后按「背景 → 流程 → 恢复 → 性能 → 参数 → 扩展 → 实战」组织。
一、背景:为什么需要内部两阶段提交
MySQL 的存储架构分为两层:
- Server 层:负责 SQL 解析、优化、执行,以及 binlog 的写入。
- 引擎层(InnoDB):负责数据页的持久化与 redo log 的写入。
两层各自维护一套日志。事务提交时必须让两套日志在语义上一致;若简单「先写一个、再写另一个」而不协调,崩溃后极易出现主从不一致。MySQL 在引擎内部采用内部两阶段提交(Internal 2PC)(常以 XID 关联 redo 与 binlog),而不是分布式事务里的外部 2PC 协调者模型。
1.1 两个日志的分工
| 日志 | 层级 | 作用 | 常见格式/形态 |
|---|---|---|---|
| redo log | InnoDB | 崩溃恢复,保证已提交变更可重做 | 物理日志(页修改) |
| binlog | MySQL Server | 主从复制、备份与按时间点恢复(PITR) | 逻辑日志(语句)或 ROW |
为什么不能只保留一种日志?
- redo log 与 InnoDB 紧耦合,其它存储引擎未必具备。
- binlog 属于 Server 层,与引擎解耦,复制与备份都依赖它。
- 历史演进上 binlog 更早出现,redo 后补;二者长期并存。
1.2 若无 2PC:两种顺序都会导致主从不一致
先 redo、后 binlog,中间崩溃:
- 重启后 InnoDB 可能认为事务已生效;
- 从库未收到对应 binlog → 主从分歧。
先 binlog、后 redo,中间崩溃:
- 从库已执行 binlog;
- InnoDB 侧无对应重做记录 → 主库回滚或状态滞后 → 主从分歧。
因此需要一种机制:以「binlog 是否成功落盘」作为事务对外可见/可复制的一条硬界线,并与 redo 中的事务状态对齐——这就是内部 2PC 要解决的核心问题。
二、两阶段提交核心流程
两阶段提交将一次 COMMIT 拆成 Prepare 与 Commit 两段;binlog 落盘成功通常被视为事务在复制语义上已「提交」的分水岭。
2.1 执行路径总览(含 undo)
BEGIN;
│
├─ 执行 SQL(修改 Buffer Pool 中的页)
│ ├─ 写 undo log(旧值,供回滚/MVCC)
│ └─ 变更写入内存中的 redo(随刷盘策略落盘)
│
└─ COMMIT; ← 触发内部 2PC
│
├─ [Prepare]
│ ├─ 生成全局事务标识 XID
│ ├─ 将相关 redo 刷到可恢复状态(策略受 innodb_flush_log_at_trx_commit 影响)
│ └─ 在 redo 中将事务标为 prepare,并记录 XID
│
├─ [写 binlog](关键分界)
│ ├─ Server 层写入 binlog(语句或 ROW)
│ ├─ binlog fsync(策略受 sync_binlog 影响)
│ └─ binlog 成功持久化 ⇒ 复制侧可观测到该事务
│
└─ [Commit]
├─ 将 redo 中该 XID 从 prepare 标为 commit
└─ 释放锁、返回客户端
2.2 与「UPDATE 一行」对照的简化流程图
以 UPDATE users SET balance = 900 WHERE id = 1; 为例:
客户端提交 COMMIT
↓
┌─────────────────────────────────────────┐
│ 1. Buffer Pool 中页已更新(脏页) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 2. Prepare:redo 记录 XID=100, prepare │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 3. 写 binlog(含相同 XID),fsync │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 4. Commit:redo 标记 XID=100, commit │
└─────────────────────────────────────────┘
↓
返回客户端 OK
2.3 关键概念小结
| 概念 | 含义 |
|---|---|
| XID | 贯穿 redo 与 binlog 的事务标识,崩溃恢复时用来对齐两边 |
| prepare | redo 上的中间态:已做好提交准备,等待 binlog 侧「落锤」 |
| binlog fsync | 成功后,即使随后崩溃,恢复逻辑也倾向提交该事务,以与复制一致 |
| commit(redo) | redo 上的终态,表示该事务在引擎侧已完成提交闭环 |
三、崩溃恢复(Crash Recovery)
重启时 InnoDB 扫描 redo,对仍处于 prepare 的事务,用 binlog 中是否存在对应 XID 决定提交还是回滚。
3.1 决策原则
| redo 状态 | binlog 中是否存在该 XID | 恢复动作 |
|---|---|---|
| commit | - | 无需额外处理 |
| prepare | 存在且完整 | 提交(前滚补全 commit) |
| prepare | 不存在/不完整 | 回滚 |
直觉:binlog 已有 ⇒ 从库可能已执行 ⇒ 主库必须提交;binlog 没有 ⇒ 复制世界不知道该事务 ⇒ 应回滚。
3.2 典型崩溃点
| 崩溃时机 | redo | binlog | 结果 |
|---|---|---|---|
| Prepare 前 | 无 prepare | 无 | 回滚,等价于未提交 |
| Prepare 后、binlog 前 | prepare | 无 | 回滚 |
| binlog 后、redo commit 前 | prepare | 有 | 提交 |
| commit 完成后 | commit | 有 | 正常,无需特殊处理 |
3.3 时间线示例(与 XID 对照)
正常提交:
| |
Prepare 后崩溃(无 binlog): 恢复时找不到 XID → 回滚,balance 维持旧值。
binlog 已写入后崩溃(redo 仍为 prepare): 恢复找到 XID → 提交,与从库对齐。
四、性能:组提交(Group Commit)
两阶段路径上,redo 与 binlog 的 fsync 往往是吞吐瓶颈。MySQL 5.6+ 的 binlog 组提交把多个事务的刷盘合并,显著减少 fsync 次数。
4.1 三子阶段(队列协调)
| |
4.2 效果(示意)
| |
五、关键参数与「双1」
5.1 innodb_flush_log_at_trx_commit
| 值 | 行为 | 安全 | 性能 |
|---|---|---|---|
| 0 | 每秒刷盘,提交不一定刷 | 低 | 高 |
| 1 | 每次提交刷 redo | 最高 | 相对低 |
| 2 | 提交写 OS cache,按策略刷 | 中 | 中 |
5.2 sync_binlog
| 值 | 行为 |
|---|---|
| 0 | 由 OS 决定刷盘时机 |
| 1 | 每次提交 fsync binlog(最安全) |
| N | 每 N 次提交刷一次(折中) |
5.3 生产常见「双1」
| |
可最大限度避免提交成功但日志未落盘导致的主从不一致;代价是磁盘 I/O 压力上升,需结合 SSD、组提交与业务吞吐评估。
5.4 组提交相关调优(高并发可酌情调)
| 参数 | 含义 | 思路 |
|---|---|---|
binlog_group_commit_sync_delay | 等待更多事务入队的微秒级延迟 | 默认 0;高并发可尝试小幅增加以换批量 fsync |
binlog_group_commit_sync_no_delay_count | 队列达到该事务数则不再等待 | 与上一参数配合 |
六、与 undo log 的关系
内部 2PC 对齐的是 redo 与 binlog;undo 仍在事务执行阶段参与(回滚与 MVCC),职责不同:
| 日志 | 层级 | 主要职责 |
|---|---|---|
| undo | InnoDB | 回滚、MVCC 版本链 |
| redo | InnoDB | 崩溃恢复(已提交需前滚) |
| binlog | Server | 复制与 PITR |
事务提交后 undo 不再用于回滚该事务,但历史版本仍可能被读视图引用,由 purge 异步回收。
七、生产案例(为何强调双1)
7.1 主库宕机后「从库比主库多」
- binlog 已持久化,从库已应用;主库在 redo commit 前崩溃。
- 恢复时:prepare + binlog 存在 ⇒ 提交 ⇒ 主从重新对齐。
7.2 sync_binlog = 0 时崩溃
- binlog 可能留在 page cache 未落盘,主库恢复后按 redo/binlog 对齐逻辑可能回滚,而从库若已收到部分事件,易出现分歧。
- 生产一般推荐
sync_binlog=1与innodb_flush_log_at_trx_commit=1配套,除非能明确接受风险并有额外防护。
八、实战验证
8.1 查看 redo / binlog 与 XID
| |
| |
8.2 粗暴模拟崩溃(仅测试环境)
| |
8.3 主从一致性抽查
| |
8.4 监控参考
| |
九、小结
- 内部 2PC 解决的是 redo 与 binlog 的提交语义对齐,核心是 XID 与 「binlog 是否落盘」 的恢复决策。
- 崩溃恢复:binlog 有 ⇒ 提交;binlog 无 ⇒ 回滚——从而尽量维持复制一致性。
- 性能:组提交合并 fsync;安全:
innodb_flush_log_at_trx_commit=1与sync_binlog=1是生产默认基线。 - undo 负责事务级回滚与 MVCC,与 2PC 分工不同但同属事务实现。
参考资料
- InnoDB Redo Log
- The Binary Log
- 《MySQL 技术内幕:InnoDB 存储引擎》