问题

介绍下MySQL5.7中的组提交

答案

1. 核心概念

组提交(Group Commit)是MySQL用于优化事务提交性能的机制,核心思想是:将多个事务的日志刷盘操作合并为一次,减少磁盘I/O次数

为什么需要组提交?

传统方式(无组提交):
事务1: 准备 → 刷redolog → 刷binlog → 提交    耗时:2次fsync
事务2: 准备 → 刷redolog → 刷binlog → 提交    耗时:2次fsync
事务3: 准备 → 刷redolog → 刷binlog → 提交    耗时:2次fsync
总计:6次fsync(磁盘刷新)

组提交方式:
事务1、2、3: 准备 → 一起刷redolog → 一起刷binlog → 提交
总计:2次fsync(磁盘刷新)
性能提升:3倍

2. MySQL组提交的演进历史

MySQL 5.5及之前:仅支持redolog组提交

问题:引入两阶段提交后,组提交失效

原因:prepare_commit_mutex全局锁
      每个事务提交时都要持有这把锁
      导致事务串行提交,无法并发

MySQL 5.6:binlog组提交机制(两阶段)

改进:移除prepare_commit_mutex,引入binlog组提交
阶段:
  1. Flush阶段:将binlog从cache写入文件
  2. Sync阶段:调用fsync刷盘

MySQL 5.7:优化的binlog组提交(三阶段)

改进:将Sync阶段拆分,进一步提升并发度
阶段:
  1. Flush阶段
  2. Sync阶段
  3. Commit阶段

3. MySQL 5.7组提交详细流程

三阶段流程

┌─────────────────────────────────────────────────────────────┐
│ Flush阶段(队列Leader负责)                                  │
├─────────────────────────────────────────────────────────────┤
│ 作用:将binlog从cache写入binlog文件(OS缓存)               │
│                                                              │
│ 流程:                                                       │
│ 1. 第一个到达的事务成为Leader                                │
│ 2. Leader收集一批事务(Follower)                            │
│ 3. Leader将所有事务的binlog写入文件(write操作)            │
│ 4. 更新binlog position                                       │
│                                                              │
│ 并发控制:Lock_log锁(保护binlog写入)                      │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Sync阶段(队列Leader负责)                                   │
├─────────────────────────────────────────────────────────────┤
│ 作用:将binlog从OS缓存刷到磁盘(fsync)                      │
│                                                              │
│ 流程:                                                       │
│ 1. Leader等待一段时间,收集更多事务(可选)                 │
│ 2. 调用fsync()将binlog刷盘                                   │
│ 3. 所有事务的binlog一起持久化                                │
│                                                              │
│ 优化点:sync_binlog=1时,多个事务共享一次fsync              │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ Commit阶段(各事务并发执行)                                 │
├─────────────────────────────────────────────────────────────┤
│ 作用:更新事务状态,提交事务                                 │
│                                                              │
│ 流程:                                                       │
│ 1. 将redolog状态从prepare改为commit                          │
│ 2. 释放事务锁资源                                            │
│ 3. 清理事务上下文                                            │
│ 4. 返回客户端"提交成功"                                      │
│                                                              │
│ MySQL 5.7优化:各事务可以并发执行commit(无需串行)         │
└─────────────────────────────────────────────────────────────┘

Leader/Follower模式

时间轴:
T1: 事务1到达Flush阶段 → 成为Leader
    └─> 持有队列锁,开始收集Follower

T2: 事务2到达 → 加入队列成为Follower
    └─> 等待Leader处理

T3: 事务3到达 → 加入队列成为Follower
    └─> 等待Leader处理

T4: Leader完成收集,开始执行:
    ├─> Flush: 将事务1、2、3的binlog一起写入文件
    ├─> Sync: 一次fsync刷盘
    └─> Commit: 事务1、2、3并发提交

T5: 下一批事务到达,事务4成为新的Leader

4. 关键数据结构

// 组提交队列(简化)
struct MYSQL_BIN_LOG::Commit_stage_manager {
    // Flush队列
    THD *flush_queue;
    mysql_mutex_t m_lock_flush_queue;

    // Sync队列
    THD *sync_queue;
    mysql_mutex_t m_lock_sync_queue;

    // Commit队列
    THD *commit_queue;
    mysql_mutex_t m_lock_commit_queue;
};

// 每个事务(THD)的状态
struct THD {
    // 在队列中的下一个事务
    THD *next_to_commit;

    // 事务的binlog cache
    binlog_cache_data binlog_cache;

    // 是否是队列的Leader
    bool is_leader;
};

5. 性能优化参数

关键配置参数

# binlog刷盘策略
# 0: 不主动刷盘,由OS决定
# 1: 每次事务提交都刷盘(最安全,推荐)
# N: 每N个事务组提交后刷盘
sync_binlog = 1

# 组提交延迟(微秒)
# 延迟等待更多事务加入组,增大批量
# 0: 不延迟(默认)
# 建议:10-100微秒(权衡延迟和吞吐)
binlog_group_commit_sync_delay = 0

# 组提交事务数阈值
# 达到此数量立即提交,不再等待
# 0: 不限制(默认)
binlog_group_commit_sync_no_delay_count = 0

# redolog刷盘策略
# 1: 每次事务提交都刷盘(推荐)
innodb_flush_log_at_trx_commit = 1

性能调优示例

# 场景1: 高吞吐量优先(可容忍小延迟)
binlog_group_commit_sync_delay = 100  # 等待100微秒
binlog_group_commit_sync_no_delay_count = 10  # 或达到10个事务立即提交

# 场景2: 低延迟优先(默认配置)
binlog_group_commit_sync_delay = 0
binlog_group_commit_sync_no_delay_count = 0

# 场景3: 极限性能(牺牲部分安全性)
innodb_flush_log_at_trx_commit = 2  # redolog每秒刷盘
sync_binlog = 100  # binlog每100个事务刷盘

6. 性能提升效果

压测对比

# 测试场景:并发更新
# 工具:sysbench

# 无组提交(MySQL 5.5)
sysbench --test=oltp --mysql-table-engine=innodb \
  --num-threads=100 --max-requests=100000 run
# TPS: 1,200

# 有组提交(MySQL 5.7)
# 相同配置
# TPS: 8,500

# 性能提升:约7倍

组提交效率公式

理论加速比 = N / 2
N: 每组的事务数量

例如:
- 每组10个事务,加速比 = 10/2 = 5倍
- 每组100个事务,加速比 = 100/2 = 50倍

实际效果受限于:
1. 并发事务数(并发越高,组越大)
2. binlog_group_commit_sync_delay(等待时间)
3. 磁盘fsync延迟

7. 监控和诊断

-- 查看binlog组提交效果
SHOW STATUS LIKE 'Binlog%';
/*
Binlog_cache_disk_use: 0        -- binlog cache溢出到磁盘的次数
Binlog_cache_use: 150000         -- 使用binlog cache的次数
Binlog_stmt_cache_disk_use: 0
Binlog_stmt_cache_use: 0
*/

-- 查看组提交大小(Performance Schema)
SELECT * FROM performance_schema.events_transactions_summary_global_by_event_name;

-- MySQL 5.7+: 查看组提交统计
SHOW STATUS LIKE 'Binlog_group_commits';
SHOW STATUS LIKE 'Binlog_group_commit_trigger%';
/*
Binlog_group_commits: 10000              -- 组提交次数
Binlog_group_commit_trigger_count: 500   -- 达到count阈值触发
Binlog_group_commit_trigger_lock_wait: 0 -- 锁等待触发
Binlog_group_commit_trigger_timeout: 500 -- 超时触发
*/

8. 与redolog组提交的关系

完整的事务提交流程(双层组提交):

1. InnoDB层:redolog组提交
   ├─> 多个事务的redolog一起进入prepare状态
   └─> 一次fsync刷盘

2. Server层:binlog组提交(三阶段)
   ├─> Flush阶段:多个事务的binlog一起写入文件
   ├─> Sync阶段:一次fsync刷盘
   └─> Commit阶段:并发提交

3. InnoDB层:修改redolog状态
   └─> 将redolog从prepare改为commit(无需fsync)

总结:一个事务需要2次fsync(redolog + binlog),但通过组提交
     多个事务共享这2次fsync,大幅提升吞吐量

9. 常见问题

Q1: 组提交会增加事务延迟吗?

正常情况:不会(binlog_group_commit_sync_delay=0)
- 事务到达即开始处理,无额外等待

优化场景:可能会(设置了delay参数)
- 为了增大组的大小,主动延迟等待
- 延迟通常很小(几十微秒),但能显著提升吞吐

Q2: 并发低时组提交还有效吗?

效果减弱但仍有价值:
- 即使只有2-3个事务,也能节省一半fsync
- 瞬时并发高峰时效果明显

建议:
- 低并发场景:使用默认配置(delay=0)
- 高并发场景:适当配置delay参数

10. 答题总结

面试时可这样回答:

MySQL 5.7的组提交是一种批量刷盘优化机制,将多个事务的日志刷盘操作合并为一次,大幅减少磁盘I/O。

核心流程分三个阶段

  1. Flush阶段:Leader线程收集一批事务,将binlog写入文件(write)
  2. Sync阶段:调用fsync将binlog刷盘,多个事务共享一次fsync
  3. Commit阶段:各事务并发执行commit,更新redolog状态

性能提升原理:原本每个事务需要2次fsync(redolog+binlog),组提交后多个事务共享这2次fsync,并发越高效果越明显,可提升5-10倍吞吐量。

关键参数

  • binlog_group_commit_sync_delay:延迟等待更多事务(增大批量)
  • sync_binlog=1:确保每组事务都刷盘(安全)

生产环境推荐sync_binlog=1和innodb_flush_log_at_trx_commit=1,在保证数据安全的前提下,通过组提交获得最佳性能。

关键要点

  • 批量刷盘减少fsync次数
  • Leader/Follower模式实现
  • 三阶段流程(Flush、Sync、Commit)
  • 并发越高,组提交效果越好