问题背景
公司报表系统每天上午 9:00 自动生成前一天的日报,数据来源是 MySQL 从库(只读副本)。业务库采用一主两从架构,使用 GTID 异步复制。业务高峰期主库 QPS 约 5000,从库负责分担读负载和报表查询。
故障现象
2024 年 3 月 5 日上午,财务部门反馈 3 月 4 日的日报中订单总金额为 238 万元,但业务后台显示的金额是 271 万元,差异达到 12%。进一步比对发现,日报中缺失了大约 3 月 4 日晚上 20:00–22:00 之间的 40 多笔订单数据。
运维登录从库检查:
|
|
主从复制延迟 35 分钟,从库落后 9000 多个 binlog 事件未回放。
排查过程
第一步:确认延迟趋势
查看延迟变化趋势,发现并非突发:
|
|
抄送 slave delay 监控图表显示,延迟从前一天下午 6:00 开始逐步上升,到次日凌晨 2:00 达到峰值约 50 分钟后缓慢恢复,但到上午 9:00 仍未追平。
第二步:定位延迟来源
|
|
从库 SQL 线程卡在 System lock 状态,说明有大事务在回放。
第三步:分析慢回放事务
查看当前的 relay log 位置对应的 binlog:
|
|
发现一个巨大的 DELETE 语句:
|
|
该语句一次删除了 120 万行数据,在主库上执行了 2 分钟,在从库上回放需要更多时间。
第四步:确认索引缺失
|
|
create_time 字段没有索引,120 万行的 DELETE 在主库变成全表扫描,回放到从库时 SQL 线程单线程处理,遇到全表扫描就严重阻塞。
解决方案
- 紧急处理:
|
|
- 优化批量删除:将大 DELETE 拆分为小批量:
|
|
-
调整删除策略:将定时清理任务从上班时间移到凌晨 2:00–4:00,避开报表查询时段。
-
优化从库并行复制:升级 MySQL 5.7 到 8.0 并配置
slave_parallel_workers=4,启用基于 WRITESET 的并行复制。
根因分析
直接原因:一个没有索引的全表扫描 DELETE 操作在主库执行了 2 分钟,从库 SQL 线程(单线程)在回放时遇到了相同的全表扫描,处理速度跟不上主库的写入速度,延迟逐步累积到 35 分钟。
深层原因:
- 报表定时任务(DELETE 清理)和日报查询在同一个时间窗口竞争
- 缺少自动删除的索引优化审查机制
- 从库使用单线程复制(MySQL 5.7 默认),对大事务的回放能力不足
预防措施
- 索引审查:所有定时清理 SQL 上线前必须通过 EXPLAIN 审查,确保有合适索引
- 大事务拆分:任何预计影响行数 >1 万的操作必须拆分为小批量
- 主从延迟监控:延迟超过 60 秒立即告警,并联动检查慢查询日志
- 并行复制:升级到 MySQL 8.0 + WRITESET 并行复制
- 报表查询使用半同步复制:考虑对从库启用
rpl_semi_sync_slave降低延迟风险
总结
主从延迟看起来是数据库问题,实际上往往是应用层 SQL 质量问题的反映。一个没有索引、不做拆分的 DELETE 语句就足以拖垮从库复制,进而影响整个报表系统的数据准确性。数据库运维不仅仅是被动监控,更需要主动审视每一个在生产环境运行的 DML 语句的质量。