两阶段提交#
一句话答案#
MySQL 用两阶段提交保证 redo log 和 binlog 一致:redo prepare→写 binlog→redo commit,崩溃时据此判断提交或回滚。
核心要点
为什么需要两阶段提交?
如果 redo log 和 binlog 不协调提交,会出现数据不一致:
- 先写 redo log,宕机,binlog 没写:主库重启后数据有,但备库没同步,主从不一致
- 先写 binlog,宕机,redo log 没写:binlog 有记录,但主库重启后数据丢失,主从不一致
两阶段提交流程(InnoDB + MySQL Server 协调):
事务提交过程:
Phase 1(Prepare 阶段):
1. InnoDB 将 redo log 写入,状态标记为 prepare
2. redo log 刷盘(fsync)
Phase 2(Commit 阶段):
3. MySQL Server 将 binlog 写入并刷盘(fsync)
4. InnoDB 将 redo log 状态从 prepare 改为 commit
崩溃恢复逻辑:
宕机重启后扫描 redo log:
├─ redo log 状态为 commit → 事务已完成,无需处理
├─ redo log 为 prepare,且 binlog 中有对应事务记录 → 补全 commit,提交事务
└─ redo log 为 prepare,但 binlog 中无记录 → 回滚事务plaintext这样保证了 redo log 和 binlog 的最终一致性。
组提交(Group Commit):把多次 fsync 合并成一次(性能关键):
朴素两阶段提交每个事务要两次 fsync(redo prepare + binlog),高并发下磁盘扛不住。组提交让一批并发事务排队、由一个 leader 代表整组做一次 fsync,把 N 次 fsync 摊成 1 次。它把 commit 阶段拆成三段流水线,每段一个队列:
Flush 阶段 Sync 阶段 Commit 阶段
事务进入 → [写 binlog 到 page cache] → [fsync binlog 落盘] → [InnoDB 提交 redo commit]
(组1 leader 带头) (合并多事务 fsync) (按顺序逐个 commit)plaintext- Flush 阶段:把各事务的 binlog 从 cache 写入文件(page cache)。第一个进入的事务成为 leader,后续到达的 follower 挂到它后面,leader 代表整组推进
- Sync 阶段:leader 对整组的 binlog 执行一次 fsync,一次刷盘搞定一批事务的 binlog —— 这就是组提交省 IO 的核心
- Commit 阶段:InnoDB 层逐个把 redo log 状态改成 commit
- 三段流水线:三个阶段各自独立,组1 在 Sync 时组2 已能在 Flush,吞吐进一步提升
binlog_order_commits=ON(默认):保证 binlog 写入顺序 == InnoDB 提交顺序 == redo 顺序。这对从库一致性至关重要——否则从库按 binlog 顺序回放、主库却按另一顺序提交,会出现数据不一致;也是 5.7 LOGICAL_CLOCK 并行复制能基于”组提交分组”判定无冲突的前提- 调参:
binlog_group_commit_sync_delay(等待 N 微秒攒更多事务再 fsync)、binlog_group_commit_sync_no_delay_count(攒够 N 个不再等)—— 用一点延迟换更大的合并批量
面试回答(2分钟版)
MySQL 的两阶段提交是 InnoDB 和 Server 层之间保证 redo log 与 binlog 数据一致的机制。如果两者不协调,主库重启后的数据和 binlog 同步到从库的数据会不一致。具体流程分两个阶段:Prepare 阶段先把 redo log 写入并标记为 prepare 状态然后刷盘;Commit 阶段先写 binlog 并刷盘,再把 redo log 状态改为 commit。崩溃恢复时按这个逻辑判断:如果 redo log 已经是 commit 状态说明事务完成不用管;如果是 prepare 状态就去检查 binlog 中有没有对应的事务记录,有则补 commit 完成提交,没有则回滚。这样就保证了无论在哪个时间点宕机,redo log 和 binlog 最终都是一致的。性能方面,两次 fsync 刷盘是主要开销,MySQL 引入了组提交优化,把多个事务的 fsync 合并成一次来提升吞吐。
追问与易错
追问方向:
- “两阶段提交性能开销在哪?”→ 主要在两次 fsync 刷盘:redo log prepare 阶段一次、binlog 写入一次;磁盘同步是最慢的 IO 操作,高并发下成为写入瓶颈
- “组提交是什么?”→ MySQL 将多个并发事务的 fsync 操作合并为一次批量刷盘,分为 flush/sync/commit 三个阶段流水线执行:Flush 把各事务 binlog 写 page cache(第一个事务当 leader 带头)、Sync 由 leader 对整组 binlog 做一次 fsync(省 IO 核心)、Commit 逐个改 redo 为 commit;大幅减少磁盘 IO 次数提升写入吞吐
- “组提交怎么保证 binlog 和 redo 顺序一致?”→ 靠 binlog_order_commits=ON,强制 binlog 写入顺序 == InnoDB 提交顺序 == redo 顺序;否则从库按 binlog 顺序回放、主库按另一顺序提交会数据不一致。这也是 5.7 LOGICAL_CLOCK 并行复制能基于”同组提交=无冲突”判定并行的前提
- “redo log 写满了会怎样?”→ 触发 checkpoint 推进,InnoDB 必须将 Buffer Pool 中对应的脏页刷盘后才能覆盖旧的 redo log 空间;刷盘期间所有更新操作被阻塞,表现为数据库”卡顿”
易错点:
- ❌ 混淆 MySQL 两阶段提交和分布式 2PC
- ❌ binlog 和 redo log 可以不一致——两阶段就是保证一致