面试知识库
困难

两阶段提交#

一句话答案#

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 可以不一致——两阶段就是保证一致