问题
介绍下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。
核心流程分三个阶段:
- Flush阶段:Leader线程收集一批事务,将binlog写入文件(write)
- Sync阶段:调用fsync将binlog刷盘,多个事务共享一次fsync
- 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)
- 并发越高,组提交效果越好