theme: default themeName: "默认主题" title: "MySQL主从延迟飙升到600秒,一个不起眼的参数救了命"
MySQL主从延迟飙升到600秒,一个不起眼的参数救了命
去年双十一前的压测,我们遇到了一个诡异的问题:MySQL主从延迟在高峰期飙升到600秒,读写分离基本废了。DBA团队查了两天没找到根因,最后是我从一个参数入手解决的。
这个问题太典型了,我敢说90%的MySQL用户都踩过或将会踩。今天把完整排查过程写出来,下次你遇到类似的坑,至少能省两天时间。
故障现象
先说现象:
- 业务高峰期(QPS约8000),主从延迟从正常的0-1秒突然飙升
- 延迟最高到600秒,Seconds_Behind_Master持续增长
- 低峰期延迟自动恢复到0
- 主库CPU、内存、IO都正常,从库资源也很空闲
乍一看像是从库性能问题,但从库资源完全没压力啊。这就很诡异了。
常见原因排查(全部排除)
按照标准流程,我依次排查了这些常见原因:
1. 从库硬件性能差?
-- 对比主从硬件配置
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';
主从都是32核64G的机器,SSD盘,硬件完全对等。排除。
2. 大事务导致延迟?
-- 查看当前正在执行的事务
SELECT * FROM information_schema.INNODB_TRX;
没有长事务。排除。
3. 从库单线程回放瓶颈?
SHOW VARIABLES LIKE 'slave_parallel_workers';
已经设置了8个并行worker。排除。
4. 网络抖动?
主从在同一机房,内网延迟0.1ms,用iperf测试带宽也够。排除。
5. binlog格式问题?
SHOW VARIABLES LIKE 'binlog_format';
已经是ROW格式,没问题。排除。
到这里,常规排查全部走完了,没找到原因。DBA团队就是在这一步卡住的。
突破口:看relay log的写入和回放速率
常规排查走不通,我换了个思路——不盯着"延迟"看,而是盯着"吞吐"看。
# 在从库上持续观察relay log的大小变化
watch -n 1 'ls -lh /var/lib/mysql/mysql-relay-bin.* tail -5'
发现一个有趣的现象:主库的binlog写入速度很快,但从库的relay log回放速度明显跟不上。具体来说:
- 主库每秒产生约5MB的binlog
- 从库每秒只能回放约2MB的relay log
但问题又来了——从库已经开了8个并行worker,为什么回放还是这么慢?
深挖并行回放的瓶颈
关键来了。我查看了并行worker的实际工作状态:
-- 查看并行回放状态
SHOW PROCESSLIST;
发现8个worker中,大部分时间只有2-3个在干活,其他都在等。这说明并行度并没有真正发挥出来。
为什么?这就涉及到MySQL并行回放的一个重要机制——基于组提交的并行回放(MTS)。
MySQL的并行回放有一个前提条件:只有同一个组提交(group commit)里的事务,才能并行回放。如果主库的组提交效率低,每次组提交里只有1-2个事务,那从库开再多的worker也没用。
查一下组提交的情况:
-- 查看组提交的统计
SHOW GLOBAL STATUS LIKE 'Binlog_group_commits%';
结果触目惊心:
Binlog_group_commits: 1583247
Binlog_group_commit_trigger_count: 0 Binlog_group_commit_trigger_timeout: 1583247
几乎所有的组提交都是靠timeout触发的! 也就是说,几乎每次组提交只包含1个事务。
这就是根因:主库的组提交效率极低,导致从库无法有效并行回放。
神奇的参数:binlog_group_commit_sync_delay
找到了根因,解决方案就呼之欲出了。MySQL提供了一个参数来优化组提交:
-- 增加组提交的等待时间,让更多事务合并到同一个组
SET GLOBAL binlog_group_commit_sync_delay = 1000; -- 等待1毫秒 SET GLOBAL binlog_group_commit_sync_no_delay_count = 10; -- 或者凑够10个事务
`binlog_group_commit_sync_delay` 的含义是:在刷盘前额外等待N微秒,让更多事务凑到一起提交。这个等待对主库的写入延迟影响微乎其微(1毫秒),但对组提交的效率提升巨大。
修改后的效果:
SHOW GLOBAL STATUS LIKE 'Binlog_group_commits%';
Binlog_group_commits: 892341
Binlog_group_commit_trigger_count: 654832 Binlog_group_commit_trigger_timeout: 237509
现在有73%的组提交是靠凑够事务数触发的,说明每次组提交包含了更多事务。
主从延迟从600秒降到了3秒以内。参数调优的细节
这个参数不是越大越好。我测试了几个值:
| 参数值 | 组提交效率 | 主库写入延迟 | 主从延迟 |
| 0(默认) | 极低 | 最低 | 600s |
| 500μs | 一般 | 几乎无感 | 45s |
| 1000μs | 较好 | 几乎无感 | 3s |
| 5000μs | 很好 | 略有感知 | 0.5s |
| 10000μs | 极好 | 明显感知 | 0s |
最终我们选择了1000μs(1毫秒),在组提交效率和主库延迟之间取得了平衡。对你的业务来说,最优值可能不同,建议从1000开始测试。
还有一个配套参数值得调整:
-- 增加每次sync的binlog大小,减少fsync次数
SET GLOBAL sync_binlog = 100; -- 默认1,改为100
注意:`sync_binlog=100`意味着断电可能丢失最近100个事务的binlog。对于大多数业务来说可以接受,但金融场景要谨慎。
还有一个隐藏坑:从库的并行模式
光优化主库还不够,从库的并行回放模式也要调对:
-- 查看当前的并行模式
SHOW VARIABLES LIKE 'slave_parallel_type';
如果是`DATABASE`(默认值),改掉它:
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8;
`DATABASE`模式只能对不同数据库的事务并行,对单库多表的场景毫无帮助。`LOGICAL_CLOCK`模式才能利用组提交信息实现真正的并行。
完整的优化配置
# my.cnf 优化项
[mysqld]
主库:提升组提交效率
binlog_group_commit_sync_delay = 1000 binlog_group_commit_sync_no_delay_count = 10 sync_binlog = 100
从库:提升并行回放效率
slave_parallel_type = LOGICAL_CLOCK slave_parallel_workers = 8 slave_preserve_commit_order = 1
修改后记得重启从库的复制线程:
STOP SLAVE;
START SLAVE;
事后反思
这次排查给我几个教训:
1. MySQL的主从延迟,很多时候根因在主库而不是从库。 我们花了两天盯着从库优化,方向都错了。
2. 并行不是万能的,要看并行度能不能真正发挥。 开了8个worker但只有2个在干活,问题不在worker数量,而在并行粒度。
3. 组提交是个被严重忽视的机制。 大多数MySQL调优文章都在讲索引、缓冲池,很少有人提组提交。但在主从架构下,它可能是影响最大的参数之一。
4. 压测要模拟真实场景。 我们的压测一直没问题,因为压测工具的事务都是单线程串行执行的,根本测不出组提交的问题。真实业务的并发模式完全不同。
5. 监控要覆盖组提交指标。 从那以后,我们把`Binlog_group_commits`相关指标加到了监控大盘上,再也没被这个问题打了个措手不及。
做IT这么多年,见过太多"早知道就好了"的情况。
希望这篇文章能帮你少走弯路。如果真的遇到问题,别一个人扛着——易云城IT服务随时待命。
📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com 🌐 https://www.eycit.com
您身边的IT专家。