从库延迟一小时,我排查到binlog才发现问题(附排查命令)

简介: 从一次主从延迟的生产事故切入,讲清MySQL主从复制原理(binlog、relay log、三线程、同步模式),拆解Seconds_Behind_Master的坑与延迟定位方法,给出主从不一致的发现与修复流程和避坑清单。

大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。

周三下午,运营跑来说报表数据不对。一看,从库上的报表比主库少了一个小时的数据。查主从状态,Seconds_Behind_Master显示0,从库却明明落后。当时我就在猜,这个"0"肯定在骗我。

今天聊主从复制。从binlog原理讲起,到延迟到底怎么查,最后说数据不一致怎么办。这篇写完,你应该能少踩我踩过的坑。

一、主从复制,靠的是binlog

先把链路讲清楚。主库写数据,会记binlog。从库拉binlog,回放,数据就同步过去了。整个过程有三个线程在跑。

主库一个dump线程,专门把binlog发给从库。从库两个,一个IO线程,负责把binlog拉下来存成relay log(中继日志)。一个SQL线程,负责把relay log里的日志一条条回放。

主库                        从库
[binlog] --dump线程--> [relay log] --SQL线程--> [数据]
                         ↑IO线程

复制链路慢,就慢在这三个环节的某一个。要么主库发不过来,要么从库拉不过去,要么从库回放不过来。

从库回放不过来,是现代主从延迟最常见的根因。MySQL 8.0提供了WRITESET并行复制。通过行级冲突判断,让互不冲突的事务并发回放,比传统的按库并行效率高得多。如果从库延迟长期降不下来,优先检查并行复制策略是否开启(slave_parallel_type=LOGICAL_CLOCKslave_parallel_workers>0)。

binlog有三种格式:statement、row、mixed。statement记SQL原文,row记行的变更,mixed两者混用。现在主流是row,它能保证从库结果和主库一致。statement格式在遇到不确定函数(比如UUID()NOW())时,会复制出不同的结果。这个后面排查会用到。

二、延迟的真相:Seconds_Behind_Master不靠谱

很多人查延迟,就看一个值:Seconds_Behind_Master。show slave status里的这个字段。我踩过的坑就是它。

这个值怎么算的?从库当前时间,减去SQL线程正在执行的binlog事件的时间戳。看起来直观,坑也在这。

第一个坑,它只算SQL线程回放的时间差。如果relay log还没拉完,或者SQL线程卡住了,这个值会显示0或者很小,但实际数据早就落后了。我那次就是,IO线程没问题,SQL线程也没报错,但它处理的binlog位置,落后主库一截,值却显示0。

还有一个坑,它不反映IO拉取的滞后。如果主库binlog发不过来,从库IO线程在等,这个值也可能是0。

所以查延迟,别只看一个字段。要看三个位置:主库当前binlog位点,从库IO线程拉到的位点,从库SQL线程回放到的位点。三个一对比,卡在哪一环节,一目了然。

-- 主库看当前binlog位点
SHOW MASTER STATUS;
-- File: mysql-bin.000012, Position: 4587520

-- 从库看IO线程和SQL线程的位点
-- MySQL 8.4及以上版本中,SHOW SLAVE STATUS已废弃,请使用SHOW REPLICA STATUS
SHOW SLAVE STATUS\G
-- Master_Log_File / Read_Master_Log_Pos   ← IO线程拉到的位置
-- Relay_Master_Log_File / Exec_Master_Log_Pos ← SQL线程回放到的位置

主库Position是4587520,从库Exec_Master_Log_Pos如果差了一大截,那就是回放落后了。再配合SHOW PROCESSLIST看SQL线程在跑什么,是不是有个大事务卡住了回放。

如果想获得更准确的延迟数据,可以用Percona Toolkit的pt-heartbeat。它在主库定期写入带时间戳的心跳记录,从库读取这个时间戳来计算真实延迟,比Seconds_Behind_Master靠谱得多。生产环境建议长期开着。

三、同步模式:异步、半同步、GTID

延迟排查完了,再说怎么从根上降下来。同步模式很关键。

模式 主库行为 丢数据风险 适用场景
异步 写完binlog就返回 可能丢 延迟敏感
半同步 等从库ACK才返回 基本不丢 高可用
GTID 全局事务ID同步 配合以上 排查方便

异步复制,主库写完binlog就返回,不管从库收没收到。快,但可能丢数据——主库一挂,没发出去的binlog就没了。

半同步复制,主库要等从库ACK确认收到binlog,才返回事务成功。多一次网络往返,慢一点,但不会丢。

GTID,全局事务标识。每个事务一个唯一ID,从库按GTID同步,不怕丢事务、不怕重复执行。排查起来也方便,能精确定位到哪个事务没同步。

GTID更大的价值在高可用切换。有了GTID,从库提升为主库后,其他从库可以通过GTID自动找到新的同步位点,不需要人工去记binlog文件名和Position。故障切换快得多,也稳得多。

我的建议:追求高可用、怕丢数据,上半同步。事务量大、延迟敏感,评估一下异步加GTID。别裸跑异步还不加GTID,出事连排查的抓手都没有。

四、数据不一致,怎么发现和修复

延迟好说,数据不一致才头疼。主从不一致,往往是历史原因——早年statement格式复制、跳过错误、手动改过从库数据。等发现时,可能已经差了很多。

发现的手段,用工具做数据校验。我最常用pt-table-checksum,它能逐行比对主从的数据,找出哪些行不一致。比对完,再用pt-table-sync把从库修回来。这两个工具的作用很直接:查差异、修差异。

# 比对主从数据一致性(Percona Toolkit)
pt-table-checksum --databases=appdb --tables=orders

跑完会输出哪些表、哪些行checksum对不上。定位到具体行,再决定怎么修。

修复有个原则:别直接改从库。从库修完,主库再写一次,又覆盖回去了。正确做法是重新同步那部分数据,让从库追上主库。真差得太多,重新搭从库都比手动改靠谱。

五、避坑清单

别只看Seconds_Behind_Master。它只反映SQL线程回放的时间差,IO拉取滞后、relay log没到,它都可能是0。查延迟,一定拿主库位点、IO位点、SQL位点三个对比着看。我那次就是被"0"骗了,白查了半天。

主从不一致,也别直接改从库。手动改完,主库再写一次,又从主库同步回来了。正确做法是定位差异行,重新同步,让从库追上。差异太大,直接重搭从库,比修还快。

跳过复制错误要极谨慎。复制卡住报错,很多人图省事STOP SLAVE再SET GLOBAL sql_slave_skip_counter=1。注意:MySQL 8.0+里sql_slave_skip_counter已废弃,要用REPLICA_SKIP_COUNTER。但无论用哪个,跳过错误都是险招——跳过一时爽,主从不一致的火葬场。真遇到报错,先看清楚是什么错,再决定跳不跳。

写在最后

主从复制这套机制,说到底是数据一致性的工程。数据在多个地方,就得保证它一致,链路看着长——binlog、relay log、位点、数据,绕来绕去都是这一件事。

现在云平台越来越多,主从切换、读写分离都托管了。但"为什么会延迟""数据怎么会不一致"这种底层判断,还是得自己懂。工具能替你干活,但判断这关,绕不过去。

希望你下次遇到延迟,能少走我走过的弯路。


你被主从延迟坑过吗?从库读到过脏数据没?评论区聊聊。我猜不少人都经历过"从库数据不对,排查半天"的时刻。

我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋

相关文章
人工智能 缓存 前端开发
11944 62
人工智能 JavaScript 开发工具
4781 17
Web App开发 人工智能 API
1349 1
人工智能 Java BI
1425 1
开发工具 Swift git
1954 6
人工智能 JavaScript 测试技术
2338 2
人工智能 自然语言处理 安全
896 0
人工智能 JavaScript 测试技术
1168 4
缓存 JavaScript Shell
2094 3