主从延迟排查全流程:从3秒到毫秒级的实战方案(附诊断命令+监控告警+避坑清单) 大家好我是数据库小学妹 上个月大促那天凌晨两点监控群突然弹了一条告警“库存数据异常”。我连上系统看了一眼客服后台查不到刚下的订单仓库那边库存没扣减前端页面还显示有货。就这么短短几分钟超卖了三百多单。老板凌晨三点打电话问我怎么回事我脑子里第一个念头是binlog断了。后来查了一圈不是断是慢。读写分离延迟从平时的几十毫秒飙到了 3.2 秒。3秒听起来不多但高并发场景下3秒足够让用户看到完全过期的数据。用户下单时读到的是3秒前的库存库存明明已经扣完了读到的还是有货。这就是超卖的根因。之前我们聊过主从复制的原理和搭建也讲了主从延迟怎么监控。但延迟真正出了事、影响到业务的时候到底怎么排查怎么治理今天把这次排障的完整过程整理出来。有读写分离架构的朋友建议收藏关键时刻翻出来能用。一、延迟从哪来——四个源头叠加读写分离的基本逻辑不难。主库负责写从库负责读。主库的变更通过 binlog 传给从库从库拿到 binlog 后重新执行一遍这个重放过程就是延迟的诞生地。第一个源头网络传输。binlog 从主库到从库要过网络同城机房还好跨地域的话单次延迟可能几十毫秒。大促期间 binlog 量暴增传输队列一积压延迟就往上爬。第二个源头从库回放瓶颈。这是最常见的瓶颈点。主库可以并发写入性能很高。但从库默认是单线程回放 binlog。主库一秒写入一万条从库一秒只能回放一千条差距越拉越大。我之前以为开了并行复制就万事大吉了结果发现配的粒度不对实际上还是单线程在跑。第三个源头大事务阻塞。主库跑一个批量更新十万行的事务binlog 会一次性发给从库。从库得花很长时间慢慢回放回放期间其他小事务都得排队。第四个源头DDL 操作。主库执行 ALTER TABLE 加索引在主库上可能走 Online DDL 很快。但传到从库后从库要重建整张表期间所有读请求都会被阻塞。这四个源叠加在一起延迟就从毫秒级变成秒级大促期间尤其明显。二、排查过程从报警到定位根因接到报警后我按先看延迟有多大再找延迟从哪来的思路一步步查。第一步确认延迟量级-- 在从库执行SHOWSLAVESTATUS\G重点看Seconds_Behind_Master。那天查出来是 3.2 秒。这里有个细节Seconds_Behind_Master是从库当前时间戳减去它正在重放的那条 binlog 事件的时间戳。如果 binlog 传输断了网络问题或者主库 binlog 被清理了这个值会显示 NULL。所以看到 NULL 不是延迟无限大是同步链路断了得先查网络。第二步定位阻塞源SHOWPROCESSLIST;从库的 SQL 线程一直在跑同一条 UPDATE。查了一下是运营凌晨跑的批量更新活动更新了二十万行商品数据。这个大事务还没回放完后面所有的操作都在等它。这里要注意一个容易搞错的点SHOW PROCESSLIST里看到 SQL 线程在跑不代表它跑得快。要看Relay_Log_Space和Exec_Master_Log_Pos的差距才能判断回放进度。第三步查并行复制配置SHOWVARIABLESLIKEslave_parallel%;结果让我有点意外。slave_parallel_type配的是 DATABASE 级别。也就是说每个数据库只有一个回放线程。这个客户所有表都在同一个库里所以实际上还是单线程回放。MySQL 8.0 的并行复制有两种策略DATABASE按库并行不同库的事务可以并发回放。单库场景下等同单线程。LOGICAL_CLOCK按主库的并行组回放同一组内无冲突的事务可以并发。这是真正的并行。WRITESET8.0.20基于行哈希判断冲突比 LOGICAL_CLOCK 更细粒度但对无主键表无效。改成 LOGICAL_CLOCK 后从库可以并发回放同一库下不同表的事务回放能力提升好几倍。第四步确认 binlog 格式SHOWVARIABLESLIKEbinlog_format;确认是 ROW 格式这个没问题。但 ROW 格式有个特点每条变更都记录完整的行数据。批量更新二十万行binlog 体积非常大传输和回放都更慢。如果是 STATEMENT 格式binlog 只记录 SQL 语句本身体积小很多但 ROW 更安全能避免存储过程和函数导致的复制不一致。所以不能为了延迟改回 STATEMENT得从其他地方想办法。第五步应用层路由逻辑翻了开发团队的代码他们的读写分离路由非常简单所有 SELECT 走从库所有 INSERT/UPDATE/DELETE 走主库。但有个致命问题。用户下单后立刻查询订单状态这个查询也走了从库。订单刚写入主库binlog 还没传到从库当然查不到。这就是典型的写完立刻读场景。我之前做项目的时候也犯过这个错——以为读写分离中间件会自动处理这种情况实际上大部分中间件默认不会做会话内读主库的判断得自己写规则。三、三层治理方案排查完根因接下来就是治理。我分了三层来做每层解决不同的问题。架构层开启并行复制STOP SLAVE;SETGLOBALslave_parallel_typeLOGICAL_CLOCK;SETGLOBALslave_parallel_workers4;STARTSLAVE;worker 数量别拍脑袋设。经验值是 CPU 核数的 1/2 到 2/3。4核机设2-3个8核机设4-5个。设太多反而会有线程切换开销得不偿失。如果 MySQL 版本是 8.0.20可以考虑 WRITESET 模式冲突检测更精确。但前提是所有表都有主键——没主键的表在 WRITESET 模式下哈希值算不出来冲突检测直接失效。这又是一个所有表必须有主键的理由。配置层半同步复制兜底并行复制解决了回放速度但没法保证从库跟得上。万一网络抖动或者主库写入暴增从库还是可能落后。这时候需要半同步复制来兜底。半同步的机制是主库写完数据后至少等一个从库确认收到 binlog才返回给应用。这样从库不会落后太多最坏情况也就是一个网络 RTT 的延迟。-- 主库INSTALL PLUGIN rpl_semi_sync_masterSONAMEsemisync_master.so;SETGLOBALrpl_semi_sync_master_enabled1;SETGLOBALrpl_semi_sync_master_timeout1000;-- 从库INSTALL PLUGIN rpl_semi_sync_slaveSONAMEsemisync_slave.so;SETGLOBALrpl_semi_sync_slave_enabled1;timeout 设为 1000 毫秒。如果从库 1 秒内没确认主库自动降级为异步复制不影响业务可用性。这个降级机制很重要——半同步不能变成不同步就卡死否则从库一挂主库也跟着瘫。实际部署中我习惯用SHOW STATUS LIKE Rpl_semi_sync%;监控降级次数。如果Rpl_semi_sync_no_tx持续增加说明从库经常超时得查网络或者考虑增加从库节点。应用层关键读操作走主库这是最关键的一步。不是所有读都必须走从库有几类读操作必须读主库写完立刻读的场景比如下单后查订单状态强一致性要求的场景比如支付后查余额数据量不大的读操作主库完全扛得住开发团队加了一个路由规则带强一致标记的查询直接路由到主库其他普通查询才走从库。改完这一行代码超卖问题再没出现过。这里顺便提一下金仓数据库在读写分离集群场景下提供了会话级的一致性读路由能力同一会话内的写后读自动路由到主库不用开发在应用层手动标记。这种底层能力能省不少代码但也别完全依赖——应用层的判断逻辑还是要有的毕竟不是所有中间件都提供这种能力。四、延迟容忍度怎么定这个问题没有标准答案得看业务类型。强一致场景延迟容忍度是零。用户下单、支付、扣库存这些必须立刻读到最新数据一秒都不能等。这类读操作直接走主库。最终一致场景延迟容忍度是秒级。商品详情页的销量数字用户看到已售1000件和已售1005件差几秒完全不影响购买决策。这类读操作走从库没问题。离线分析场景延迟容忍度可以到分钟级。BI 报表、数据看板跑从库完全没问题。判断标准其实就一句话这个数据晚几秒读到业务会不会出问题会出问题就读主库不会就交给从库。别搞一刀切。五、日常监控怎么做别等出事了才去查延迟平时就得监控起来。必看的三个指标-- 1. 延迟秒数SHOWSLAVESTATUS\G-- 关注 Seconds_Behind_Master-- 2. 从库回放状态SHOWSLAVESTATUS\G-- 关注 Slave_SQL_Running 和 Slave_IO_Running 是否都为 Yes-- 3. 主从 binlog 位置对比SHOWMASTERSTATUS;SHOWSLAVESTATUS\G-- 对比 Exec_Master_Log_Pos 和主库的 Position告警阈值建议指标预警线报警线Seconds_Behind_Master1秒5秒Slave_SQL_Running / Slave_IO_Running断开立即预警断开立即报警主从 binlog 位置差超过100MB超过500MB定时巡检脚本#!/bin/bash# 每5秒检查一次延迟whiletrue;dodelay$(mysql-eSHOW SLAVE STATUS\G|grepSeconds_Behind|awk{print $2})echo$(date): 延迟${delay}秒if[$delay-gt5];thenecho告警延迟超过5秒# 这里可以加邮件或钉钉告警fisleep5done配合 Prometheus mysqld_exporter 做持久化监控Grafana 面板上同时放延迟趋势、binlog 位置差、回放速率三个指标一目了然。六、延迟治理避坑清单这几年做读写分离踩过的坑不少整理几条出来。大事务放在业务高峰期跑。运营同学喜欢在白天跑批量更新觉得方便。但大事务的 binlog 在从库回放很慢高峰期从库本来就吃紧再来个大事务直接雪崩。批量操作一定要放到低峰期凌晨两三点跑没人跟你抢资源。写完立刻读还走从库。这个前面讲过了但真的太多人犯这个错。用户刚注册完跳个人中心刚下完单跳订单详情这些场景千万别走从库。直接读主库性能完全扛得住。以为开了并行复制就一劳永逸。这是最大的误区。并行复制不是万能药它只解决回放速度不解决网络延迟也不解决大事务。而且并行复制本身也有坑——没主键的表在 WRITESET 模式下会出问题worker 线程设太多反而性能下降。得配合半同步复制和应用层路由一起用才能真正稳。从库当备份库用。有些团队把从库用来跑定时报表、导出任务甚至在上面建临时表。这些操作不占用主库资源但会消耗从库的回放资源。从库被业务查询拖慢延迟自然就上来了。建议单独搭一个分析节点别跟复制从库混用。监控只看 Seconds_Behind_Master。这个指标有个致命缺陷它依赖从库的系统时间。如果主从服务器时钟不同步这个值就不准。所以一定要同时看 binlog 位置差用Exec_Master_Log_Pos和主库Position的差距来判断这个才是客观的。总结读写分离延迟治理核心就三句话并行复制解决回放瓶颈别让单线程拖后腿。半同步复制做兜底保证从库不会落后太多。应用层路由最关键强一致读主库最终一致走从库。这三层配合起来延迟基本能控制在毫秒级。只靠调数据库参数是解决不了根本问题的得从架构、配置、应用三个维度一起动手。你在读写分离架构里遇到过什么坑延迟最高飙到过多少评论区一起交流咱们互帮互助我是数据库小学妹咱们下篇见 本文案例基于 MySQL 8.0不同数据库版本略有差异。核心思路通用具体命令请参考对应数据库文档。