先说结论:主从复制延迟飙到八万秒的时候,最不该怀疑的就是网络。我花了三个小时才明白这个道理,期间还差点让客户把机房运维叫起来背锅。
事情是这样的。上周三下午四点,监控弹出一条告警:MySQL 从库复制延迟 83012 秒。八万秒,差不多一天。我第一反应是网络抖动——从库和主库不在一个机柜,中间隔了两层交换机,之前也闹过几次小延迟,几十秒那种,过会儿自己就追上了。但这次是八万秒,明显不对劲。
我远程上去,先看了复制状态:
SHOW SLAVE STATUS\G
Seconds_Behind_Master: 83012
Relay_Log_Space: 47.8G
看到 Relay_Log_Space 47.8G 的时候我愣了一下。从库的 relay log 堆了快 48G,说明 SQL 线程已经很久没消费了。这不是网络问题,网络只影响 IO 线程拉取,不会让 relay log 堆这么高。我判断错了方向。
赶紧看 SQL 线程报什么错:
Last_SQL_Error: Error 'Duplicate entry '10293847' for key 'PRIMARY'' on query...
Duplicate entry。主键冲突。这就很尴尬了——从库上竟然有和主库冲突的数据。不用猜,肯定是之前有人手动在从库上执行过 insert,或者哪个定时任务误连了从库写数据。
这种错误最烦人,因为 binlog 里那条语句在从库重放不了,SQL 线程直接卡死。而 IO 线程还在不停拉新 binlog,relay log 就越堆越多,八万秒就是这么攒出来的。
处理办法其实就三步:
第一步,先确认冲突范围。查从库这张表里有没有独有数据:
SELECT COUNT(*) FROM orders WHERE id > 10293840;
如果冲突就一两行,直接删掉从库上多余的那行,然后 SET GLOBAL sql_slave_skip_counter = 1 跳过,或者干脆用 MySQL 8.0 的 CHANGE REPLICATION SOURCE TO ... SOURCE_IGNORE_SERVER_IDS 配合跳过。
第二步,跳过卡住的语句后,SQL 线程会开始追 relay log。但 47.8G 的积压,直接追会拖垮从库 IO。稳妥做法是先停掉 SQL 线程,评估追平需要多久,必要时直接重建从库——全量备份 + binlog 追到当前位点,比硬追 47.8G 快得多。
那天我选了重建。mysqldump 全量导出来大概 40 分钟,然后 change master 指到当前位点,从库追了不到 20 分钟就追平了。比让它自己啃 47.8G relay log 靠谱。
第三步也是最重要的一步,防止再犯:从库账号只给 SELECT 权限,业务账号一律指主库;定时任务里凡是写库的,全部核对一遍连接串;顺便把监控加上 Seconds_Behind_Master 超过 300 就告警的规则——以前阈值设的是 3600,八万秒才被发现,阈值太宽等于没有。
最后说说那次差点甩锅的事。我跟机房说网络有问题,机房查了半天说交换机和光模块都正常,最后发现是我自己误判。后来跟客户解释,客户倒是没说什么,但我自己挺臊的。运维这行,最怕的不是故障,是方向错了还硬着头皮往前冲。看告警先看最可能的原因,但也要留个心眼——有时候你觉得最不可能的那个原因,恰恰就是真相。
评论