05.4 - 两阶段提交与崩溃恢复
定位:两阶段提交是 Redo Log 和 Binlog 保持一致的"同步协议"——理解它才能理解 MySQL 崩溃后如何恢复到正确状态 面试高频度:⭐⭐⭐⭐⭐ 考查方式:为什么需要 2PC、prepare 和 commit 阶段细节、崩溃恢复的三种场景、如何保证主从一致
一、这是什么?为什么需要它?
是什么
两阶段提交(2PC, Two-Phase Commit)是 MySQL 为了保证 Redo Log 和 Binlog 状态一致而设计的提交协议。
为什么需要 2PC?
如果不需要 2PC,崩溃后会出现什么问题?
sql
-- 假设事务 T 执行: UPDATE users SET age=30 WHERE id=1
-- 场景 1: 先写 Redo Log,后写 Binlog
-- 写完 Redo Log 后崩溃 → Binlog 没有记录
-- → 恢复后: 数据改了 (Redo Log 重放) 但 Binlog 没记录
-- → 从库复制 Binlog → 主从不一致!
-- 场景 2: 先写 Binlog,后写 Redo Log
-- 写完 Binlog 后崩溃 → Redo Log 没有记录
-- → 恢复后: 数据没改 (Redo Log 没有重放) 但 Binlog 有记录
-- → 从库会执行这个事务 → 主从不一致!2PC 的解决方案:把提交分成两个阶段,确保 Redo Log 和 Binlog 要么都写入,要么都不写入。
核心 insight:2PC 解决的本质上是一个"分布式事务"问题——Redo Log 和 Binlog 是两个独立的写入通道,需要协调器来保证它们的一致性。
二、原理拆解
2.1 两阶段提交流程
2PC 提交过程
事务执行完成后,进入提交阶段:
┌─────────────────┐
│ 事务开始执行... │
│ UPDATE ... │
│ UPDATE ... │
└────────┬────────┘
│
▼
┌─────────────────┐
Phase 1 │ Prepare 阶段 │
└────────┬────────┘
│
┌───────┴────────┐
│ Redo Log 写入 │
│ 状态: prepare │
│ (事务ID+修改) │
└───────┬────────┘
│ 成功
▼
┌─────────────────┐
Phase 2 │ Commit 阶段 │
└────────┬────────┘
│
┌───────┴────────┐
│ 1. Binlog 写入 │ ← 此时崩溃?→ 恢复逻辑看后面
│ 2. Binlog 刷盘 │
└───────┬────────┘
│ 成功
▼
┌─────────────────┐
│ Redo Log 标记 │
│ 状态: commit │
└─────────────────┘
│
▼
┌─────────────────┐
│ 事务提交成功 ✅ │
└─────────────────┘2.2 三个关键状态
事务状态转换:
┌─────┐
│ 执行中 │ ← DML 操作
└──┬──┘
│
┌──▼──┐
│Prepare│ ← Redo Log 写入 prepare 状态
└──┬──┘
│
┌─────┴──────┐
│ │
┌───▼──┐ ┌───▼──┐
│ Binlog│ │崩溃!│
│ 写入 │ └──┬───┘
└───┬──┘ │
│ ┌──▼──┐
┌───▼──┐ │恢复 │
│Commit │ │判断 │
└───┬──┘ │ │
│ ┌┴┐ ┌┴┐
┌───▼──┐ │Bin│ │Bin│
│完成 ✅│ │log│ │log│
└──────┘ │有?│ │无?│
└┬─┘ └┬─┘
┌──┴──┐┌──┴──┐
│提交 ││回滚 │
└─────┘└─────┘2.3 崩溃恢复的三种场景
崩溃发生在哪个阶段? → 恢复时不同的处理:
场景 A: Prepare 前崩溃
┌──────────┐
│ 执行中... │ ← 还没进入 Prepare
└──────────┘
⇒ 回滚 —— Redo Log 无记录,Binlog 无记录
⇒ 该事务的修改全部丢弃
场景 B: Prepare 后、Binlog 写入前崩溃
┌────────────┐
│ Redo Log: Prepare ✅ │
│ Binlog: ❌ 未写入 │
└────────────┘
⇒ 回滚 —— Redo Log 有 prepare 但没有对应的 Binlog
⇒ InnoDB 认为事务未提交 → 用 Undo Log 回滚
场景 C: Binlog 写入后、Commit 前崩溃
┌────────────┐
│ Redo Log: Prepare ✅ │
│ Binlog: ✅ 已写入 │
│ Redo Log: Commit ❌ ← 崩溃在这里
└────────────┘
⇒ 提交 —— Redo Log 有 prepare 且 Binlog 已写入
⇒ InnoDB 认为事务已提交 → 补写 Redo Log commit 状态
⇒ 数据可见,Binlog 和主库一致 ✅
场景 D: Commit 完成后崩溃
┌────────────┐
│ Redo Log: Commit ✅ │
│ Binlog: ✅ 已写入 │
└────────────┘
⇒ 无需处理 —— 事务已完整提交 ✅2.4 Redo Log 的 prepare 和 commit 状态
Redo Log 记录格式:
prepare 状态的 Redo Log:
[TYPE=PREPARE] [TRX_ID=100] [PAGE_NO=42] [OFFSET=128] [OLD=20] [NEW=30]
commit 状态的 Redo Log:
[TYPE=COMMIT] [TRX_ID=100]崩溃恢复时:
- 扫描所有 Redo Log 中的事务
- 对于每个 prepare 状态的事务,检查 Binlog 中是否有对应记录
- 有 → 补 commit(场景 C);没有 → 回滚(场景 B)
2.5 XID 的桥梁作用
XID(事务 ID) 是连接 Redo Log 和 Binlog 的桥梁:
Redo Log: [TRX_PREPARE] [XID=12345] [修改内容]
↑ ↑
│ ← 同一个 XID │
Binlog: [XID_EVENT: 12345] [SQL/行数据]
崩溃恢复时:
扫描 Redo Log → 找到 XID=12345 的 prepare 事务
扫描 Binlog → 检查 XID=12345 是否写入
已写入 → 提交,未写入 → 回滚三、图解全景
崩溃恢复决策树
MySQL 崩溃重启
│
▼
扫描 Redo Log → 找出所有 prepare 状态的事务
│
▼
┌──────────────────────────────────┐
│ 对于每个 prepare 事务: │
│ │
│ 检查 Binlog 中是否有对应 XID │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 有 XID │ │ 无 XID │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 提交事务 │ │ 回滚事务 │ │
│ │ (补commit)│ │ (Undo回滚)│ │
│ └──────────┘ └──────────┘ │
│ │
│ ⇒ 保证 Redo Log 和 Binlog │
│ 最终状态一致 ✅ │
└──────────────────────────────────┘四、实战验证
验证同步 binlog 配置
sql
-- 查看双 1 配置
SHOW VARIABLES WHERE variable_name IN (
'innodb_flush_log_at_trx_commit',
'sync_binlog'
);模拟 Binlog 写入后崩溃的恢复
sql
-- 注意:以下无法在 SQL 层面直接模拟,需要理解原理
-- 查看 Binlog 中的 XID
SHOW BINLOG EVENTS IN 'mysql-bin.000001' LIMIT 10;
-- 使用 mysqlbinlog 解析查看
-- $ mysqlbinlog -v mysql-bin.000001 | grep XID监控复制状态
sql
-- 查看主从复制是否在正常运行
SHOW SLAVE STATUS\G
-- 重点看:
-- Slave_IO_Running: Yes ← 从库 IO 线程正常
-- Slave_SQL_Running: Yes ← 从库 SQL 线程正常
-- Seconds_Behind_Master: 0 ← 没有延迟五、面试视角
| 追问 | 答案要点 |
|---|---|
| 为什么需要 2PC? | 保证 Redo Log 和 Binlog 的一致性。否则崩溃后可能主从不一致 |
| prepare 阶段做了什么? | 写 Redo Log,标记为 prepare 状态。此时事务还没提交 |
| commit 阶段做了什么? | 先写 Binlog,再标记 Redo Log 为 commit。两个都成功才算提交 |
| Prepare 后崩溃怎么办? | 检查 Binlog 中是否有对应 XID:有则补提交,无则回滚 |
| XID 的作用? | 连接 Redo Log 和 Binlog 的桥梁,崩溃恢复时通过 XID 匹配两个日志 |
| 双 1 配置是什么? | innodb_flush_log_at_trx_commit=1 + sync_binlog=1,最安全配置 |
📚 相关链接
- Redo Log:**Redo Log**
- Undo Log:**Undo Log**
- Binlog:**Binlog**
- ACID 实现:**事务ACID实现**
- ← 返回 **日志系统索引**