大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。
周三下午,运营跑来说报表数据不对。一看,从库上的报表比主库少了一个小时的数据。查主从状态,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_CLOCK,slave_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、位点、数据,绕来绕去都是这一件事。
现在云平台越来越多,主从切换、读写分离都托管了。但"为什么会延迟""数据怎么会不一致"这种底层判断,还是得自己懂。工具能替你干活,但判断这关,绕不过去。
希望你下次遇到延迟,能少走我走过的弯路。
你被主从延迟坑过吗?从库读到过脏数据没?评论区聊聊。我猜不少人都经历过"从库数据不对,排查半天"的时刻。
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋