问题

什么是事务的2阶段提交?

答案

1. 核心概念

两阶段提交(Two-Phase Commit, 2PC) 是MySQL用来保证binlog和redolog数据一致性的内部协议。

为什么需要两阶段提交?

  • binlog是Server层日志(所有存储引擎共享)
  • redolog是InnoDB引擎层日志
  • 如果两个日志分别写入,可能出现:
    • redolog写成功,binlog写失败 → 主库数据已提交,从库数据未同步
    • binlog写成功,redolog写失败 → 从库有数据,主库崩溃恢复后数据丢失

两阶段提交确保两个日志要么都成功,要么都失败,保持逻辑一致

2. 两阶段提交流程

以一条更新语句为例:

UPDATE user SET age = 20 WHERE id = 1;

完整执行流程

阶段1: Prepare阶段
┌─────────────────────────────────────┐
│ 1. InnoDB执行更新操作                │
│ 2. 写入undolog(记录旧值)           │
│ 3. 更新Buffer Pool中的数据页         │
│ 4. 写入redolog buffer                │
│ 5. redolog进入prepare状态并刷盘     │ ← Prepare阶段
│ 6. 告诉Server层"我准备好了"         │
└─────────────────────────────────────┘
                 ↓
         【切换到Server层】
                 ↓
┌─────────────────────────────────────┐
│ 7. 写入binlog cache                 │
│ 8. binlog刷盘                        │ ← Commit准备阶段
└─────────────────────────────────────┘
                 ↓
阶段2: Commit阶段
┌─────────────────────────────────────┐
│ 9. InnoDB将redolog状态改为commit    │ ← Commit阶段
│10. 释放锁,事务完成                  │
└─────────────────────────────────────┘

关键步骤说明

Prepare阶段

  1. 写redolog,但标记为prepare状态(未真正提交)
  2. redolog持久化到磁盘

Commit阶段

  1. 写binlog并持久化到磁盘
  2. 将redolog状态从prepare改为commit
  3. 事务真正完成

3. 崩溃恢复机制

两阶段提交的核心价值在于崩溃恢复时的处理:

崩溃场景分析:

时间点A: redolog prepare之前崩溃
└─> 结果:redolog和binlog都没有,事务回滚 ✓

时间点B: redolog prepare之后,binlog写入之前崩溃
└─> 结果:redolog处于prepare状态,binlog不存在
    恢复时回滚事务 ✓

时间点C: binlog写入之后,redolog commit之前崩溃
└─> 结果:redolog处于prepare状态,binlog存在
    恢复时提交事务 ✓

时间点D: redolog commit之后崩溃
└─> 结果:redolog和binlog都有,事务已提交 ✓

恢复判断逻辑

1. 扫描redolog,找到所有处于prepare状态的事务
2. 根据事务XID去binlog中查找对应记录
3. 如果binlog存在 → 提交事务
4. 如果binlog不存在 → 回滚事务

4. 源码关键点

InnoDB事务提交的核心代码流程(简化):

// InnoDB层
int trx_commit() {
    // 1. Prepare阶段
    trx_prepare_for_mysql();  // redolog写入并标记为prepare

    // 2. 通知Server层
    ha_commit_trans();

    // 3. Commit阶段
    trx_commit_complete();    // redolog状态改为commit
}

// Server层
int ha_commit_trans() {
    // 写入binlog
    write_binlog();

    // 调用引擎commit
    ht->commit();
}

5. 性能优化:组提交

MySQL 5.6+引入组提交(Group Commit)优化两阶段提交性能:

传统方式:每个事务都要刷盘两次(redolog + binlog)

组提交:多个事务一起刷盘
┌───────────────────────────────────┐
│ 事务1: prepare → ┐                │
│ 事务2: prepare → ├─ 一起刷redolog │
│ 事务3: prepare → ┘                │
├───────────────────────────────────┤
│ 事务1: binlog → ┐                 │
│ 事务2: binlog → ├─ 一起刷binlog   │
│ 事务3: binlog → ┘                 │
├───────────────────────────────────┤
│ 事务1,2,3: commit                 │
└───────────────────────────────────┘

binlog组提交三个阶段(MySQL 5.7+):

  • Flush阶段:将binlog从cache写入文件
  • Sync阶段:调用fsync刷盘
  • Commit阶段:修改redolog状态为commit

6. 配置参数

# redolog刷盘策略
# 1: 每次事务提交都刷盘(最安全)
innodb_flush_log_at_trx_commit = 1

# binlog刷盘策略
# 1: 每次事务提交都刷盘(最安全)
sync_binlog = 1

# 组提交相关
# binlog组提交的最大等待时间(微秒)
binlog_group_commit_sync_delay = 0
# binlog组提交的最大事务数
binlog_group_commit_sync_no_delay_count = 0

7. 答题总结

面试时可这样回答:

两阶段提交是MySQL保证binlog和redolog一致性的机制。

流程分为两个阶段

  1. Prepare阶段:InnoDB将redolog写入磁盘并标记为prepare状态
  2. Commit阶段:Server层写入binlog后,InnoDB将redolog改为commit状态

核心价值在崩溃恢复:如果在binlog写入之前崩溃,事务回滚;如果在binlog写入之后崩溃,事务提交。通过redolog的prepare状态和binlog的存在性来判断事务最终状态。

性能优化:MySQL 5.6+引入组提交机制,允许多个事务一起刷盘,大幅提升并发性能。生产环境通常配置双1(innodb_flush_log_at_trx_commit=1, sync_binlog=1)保证数据安全。

关键要点

  • 解决binlog和redolog一致性问题
  • prepare → write binlog → commit三个关键步骤
  • 崩溃恢复通过XID关联两个日志
  • 组提交优化刷盘性能