MySQL 主从复制是再常见不过的架构:主库负责写,从库负责读,既能分摊读压力,又能做主库故障时的备份。可一旦出现"刚写入的数据读不到"、"报表数据比实际少了几分钟"这类现象,基本就是主从延迟在作怪。
延迟本身不一定是故障,几毫秒到一两秒通常可以接受;但当延迟持续增长到几十秒、几分钟甚至几小时,业务就会出问题——最典型的是"下单后立刻查订单查不到",因为读请求打到了还没同步的从库。这篇文章把主从延迟讲清楚:为什么会延迟、怎么检查和量化延迟、以及从并行复制、拆分大事务、binlog 格式到监控告警的一整套优化思路。
一、先理解主从复制的流程
要排查延迟,先要知道数据是怎么从主库传到从库的。MySQL 主从复制的核心是三个线程:
- 主库的 Binlog Dump 线程:把 binlog 里的变更事件发给从库。
- 从库的 IO 线程:连接主库,拉取 binlog 并写入从库本地中继日志(relay log)。
- 从库的 SQL 线程(回放线程):读取 relay log,把变更在从库上重放。
可以看到,延迟可能发生在两个环节:一是网络拉取慢(IO 线程跟不上),二是回放慢(SQL 线程跟不上)。绝大多数延迟是后者——从库回放速度赶不上主库写入速度。
传统复制中,从库只有一个 SQL 线程串行回放,而主库可能有很多并发写入线程。主库并发写入、从库单线程回放,这个结构性矛盾是延迟的根本来源。所以优化方向里,开启并行复制是第一位。
二、检查复制状态:SHOW SLAVE STATUS
排查的第一步永远是看状态。在新版 MySQL(8.0.22 起)里,SHOW SLAVE STATUS 已被 SHOW REPLICA STATUS 取代,但旧名仍可用。
-- MySQL 8.0.22 之前
SHOW SLAVE STATUS\G
-- MySQL 8.0.22 及之后(推荐)
SHOW REPLICA STATUS\G
输出里有几个字段必须看懂:
| 字段 | 含义 |
|---|---|
| Slave_IO_Running | IO 线程是否运行,必须为 Yes |
| Slave_SQL_Running | SQL(回放)线程是否运行,必须为 Yes |
| Seconds_Behind_Master | 延迟秒数,核心指标 |
| Master_Log_File / Read_Master_Log_Pos | IO 线程读到的主库 binlog 位置 |
| Relay_Log_File / Exec_Master_Log_Pos | SQL 线程回放到的位置 |
| Relay_Master_Log_File | 当前回放的 relay 对应的主库 binlog |
| Last_IO_Error / Last_SQL_Error | 错误信息,非空说明复制出错 |
| Master_Server_Id / Master_UUID | 主库标识 |
先看两个 Running 是否都是 Yes。只要有一个是 No,就不是"延迟"而是"复制中断",要立刻看 Last_IO_Error 或 Last_SQL_Error 处理错误。
三、Seconds_Behind_Master 到底准不准
Seconds_Behind_Master 是最常被使用的延迟指标,但它有明确的局限,很容易误导人。
它的计算方式是:从库当前时间,减去"正在回放的 binlog 事件时间戳"。也就是说,它衡量的是"回放位置距离当前时间差了多久"。
问题在于:
- 如果主库长时间没有写入,从库早已追上,但
Seconds_Behind_Master可能显示 0,这是正常的。反过来,如果从库和主库完全断连(IO 线程挂了),这个值可能变成 NULL。 - 它不反映网络传输延迟,只反映回放进度差。
- 多线程复制(MTR)下它可能不准,因为多个 worker 回放进度不同,这个值是估算的。
- 它只看时间,不看数据量。如果主库在几秒内写入了海量数据,从库要回放很久,但时间戳差可能一开始看起来不大。
所以不能只信这个值。更可靠的做法是下面几种:
方法一:用 pt-heartbeat 测量真实延迟
Percona Toolkit 的 pt-heartbeat 是最推荐的方案。原理是在主库上定时写一条带时间戳的心跳记录,从库读到它,用当前时间减去记录里的时间戳,得到真实延迟。
# 主库上启动心跳写入(每秒一次)
pt-heartbeat --user=root --password=xxx -D test \
--create-table --update --daemonize
# 在监控端监控从库延迟
pt-heartbeat --user=root --password=xxx -D test \
--monitor -h 10.0.0.6 --interval=1
# 只查一次延迟值(适合脚本采集)
pt-heartbeat --user=root --password=xxx -D test \
--check -h 10.0.0.6
这个方式不依赖 Seconds_Behind_Master,测出来的是"主库写入到从库可见"的真实延迟,最贴合业务感受。
方法二:对比日志位置
对比主库的 SHOW MASTER STATUS 和从库 SHOW REPLICA STATUS 的 Read_Master_Log_Pos / Exec_Master_Log_Pos,看从库落后主库多少字节和多少个 binlog 文件。这个差值能直观反映"拉取"和"回放"的进度差。
-- 主库
SHOW MASTER STATUS;
-- 从库
SHOW REPLICA STATUS\G -- 关注 Read_Master_Log_Pos 与 Exec_Master_Log_Pos 的差距
如果 Read_Master_Log_Pos 落后主库很多,说明网络或 IO 线程有问题;如果 Read_Master_Log_Pos 已追上主库、但 Exec_Master_Log_Pos 落后,说明是回放慢。
方法三:SHOW PROCESSLIST 看从库在干什么
SHOW FULL PROCESSLIST; -- 看从库上是否有长时间运行的 SQL(可能是回放的大事务)
SHOW ENGINE INNODB STATUS\G -- 看事务历史和锁等待
四、延迟的常见原因
把原因归纳成几类,对照着排查:
1. 大事务。一次更新几十万行、一次大批量导入、DELETE 没加限制条件——这些在主库上可能几秒完成,但生成的 binlog 事件要在从库串行回放,回放时间可能远长于主库执行时间。这是延迟的头号原因。
2. 从库单线程回放。没开并行复制,或者开了但因为事务没有可并行的特征(比如都有冲突)而退化为串行。主库并发写入越高,延迟越明显。
3. DDL 操作。ALTER TABLE 在从库上同样是耗时的阻塞操作,尤其大表加字段、加索引,从库回放时会长时间卡住,后面的所有变更都得排队。
4. 从库自身负载高。从库不只是回放,还要承担业务读请求。如果读请求把从库的 CPU、IO 打满,回放线程抢不到资源,延迟自然上升。把大量报表、分析类慢查询放到从库是常见诱因。
5. 网络延迟。 主从跨机房、跨地域时,网络 RTT 高会拖慢 IO 线程拉取 binlog。一般同机房延迟可忽略,跨地域要评估。
6. 参数与硬件差异。从库规格比主库小、磁盘慢、innodb_flush_log_at_trx_commit 设置更保守(为保证不丢数据设成 1,但回放时每次提交都刷盘),都会让回放变慢。从库的回放特性 slave_parallel_type、slave_parallel_workers、slave_preserve_commit_order 没配对也会拖后腿。
五、优化思路
优化一:开启并行复制
这是收益最大的一步。MySQL 的并行复制经历了几个阶段:
- 5.6:按库并行(
slave_parallel_type=DATABASE),只有不同库的事务能并行,同名库效果差。 - 5.7:基于组提交(
LOGICAL_CLOCK),同一组提交的事务可以并行回放,效果大幅提升。 - 8.0:基于 WRITESET 的并行(
slave_parallel_type=LOGICAL_CLOCK+slave_parallel_workers+ 开启binlog_transaction_dependency_tracking=WRITESET),能识别更多可并行的事务,即使在同一张表上只要不冲突也能并行。
# 从库配置(my.cnf)
[mysqld]
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 8 # 经验值:设为从库 CPU 核数的 1/2 到 1 倍
slave_preserve_commit_order = 1 # 保证提交顺序与主库一致,避免不一致
8.0 还可以配合主库设置:
# 主库(8.0)
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
注意 slave_parallel_workers 不是越大越好,设成 CPU 核数量级即可,过大反而增加调度开销。
优化二:拆分大事务
把大批量操作拆成小批次,是缓解延迟最直接的手段。比如:
-- 不推荐:一次删几百万行
DELETE FROM logs WHERE created_at < '2026-01-01';
-- 推荐:分批删除,每次几千行,中间可暂停
DELETE FROM logs WHERE created_at < '2026-01-01' LIMIT 2000;
批量导入也用分批提交的方式。应用层的批量更新同样要限制单次影响行数。核心原则:单个事务的行数不要太多,控制在几千到几万行以内(经验值),让每次回放都很快。
优化三:DDL 处理
大表 DDL 尽量用在线工具(pt-online-schema-change、gh-ost)或数据库自带的在线 DDL(MySQL 8.0 的部分操作支持 Online DDL),减少阻塞。DDL 期间要特别关注延迟,必要时在低峰期执行。另外,通过 SET sql_log_bin=0 关闭某些 DDL 的 binlog 是危险操作,会导致主从结构不一致,不要这么做。
优化四:binlog 格式
binlog 格式影响 binlog 大小和回放行为:
SHOW VARIABLES LIKE 'binlog_format';
STATEMENT:记录 SQL 语句,binlog 小,但某些函数、触发器会导致主从结果不一致。ROW:记录每行变更,binlog 大,但最安全,且是并行复制(WRITESET/LOGICAL_CLOCK)的前提。MIXED:混合。
生产环境推荐 ROW,安全且支持并行复制。虽然 binlog 体积会增大,但换来的是正确性和更好的并行回放。配合 binlog_row_image=MINIMAL 可以减小 ROW 格式的体积。
优化五:半同步复制的取舍
半同步复制(semi-sync)要求至少一个从库收到 binlog 并回 ack 后,主库才向客户端返回成功。它能减少主库宕机时的数据丢失,但会增加写入延迟(要等网络往返),并且从根本上不能消除从库回放延迟——它保证的是"从库收到了日志",不是"从库已回放完"。
所以:如果你的延迟问题来自回放慢,半同步帮不了你;如果你的诉求是减少主库崩溃丢数据,半同步是有价值的。要清楚这两者的区别,别指望用半同步解决延迟。
优化六:把读流量与回放错开
如果从库既回放又扛读,考虑:
- 把重查询(报表、全表扫描)从从库移走,用专门的只读实例或 OLAP 库。
- 从库上给回放线程留资源,必要时用
innodb_thread_concurrency等参数控制。 - 给回放线程更高的 IO 优先级(操作系统层面)。
六、业务层的兜底策略
有时候延迟无法立刻消除,这时要在业务层兜底,避免用户感知到延迟。
策略一:写后读走主库。刚写入的数据,其后的读请求强制走主库一段时间(如 1 秒内),保证读到最新数据。很多中间件(ShardingSphere、MyCat)支持这种"强制主库"的路由。
策略二:容忍延迟的业务走从库。列表页、统计报表、历史数据这类对实时性要求不高的读,走从库;下单、支付这类强一致操作走主库。
策略三:读自己的写。在会话层面记录用户刚写的记录 ID,读取时如果命中提示走主库。
策略四:数据同步延迟可容忍时,明确告知用户。比如后台报表标注"数据截至 T 时刻",避免误解。
七、监控与告警
延迟问题往往悄然而至,必须监控。核心监控项:
Seconds_Behind_Master(仅作辅助,不要单独依赖)。pt-heartbeat --check的真实延迟。- 从库 IO/SQL 线程状态(任一为 No 立刻告警)。
- 主从 binlog 位置差值。
- 从库的 IO 使用率、CPU、慢查询。
SHOW REPLICA STATUS的错误字段。
一个简单的监控脚本思路(通过 heartbeat 表):
-- pt-heartbeat 创建的心跳表
SELECT TIMESTAMPDIFF(SECOND, ts, NOW()) AS delay_seconds
FROM test.heartbeat
WHERE server_id = @@server_id;
告警阈值经验值:延迟超过 5 秒警告,超过 30 秒严重,持续超过几分钟应触发值班。同时必须监控复制线程是否中断,这比延迟更严重。
常见问题
Q:Seconds_Behind_Master 显示 0,但业务读不到刚写的数据?
可能不是回放延迟,而是多线程复制下该指标不准,或者读请求打到了不同的从库(有的从库没配好)。用 pt-heartbeat 测真实延迟,并检查是否所有从库都在正常复制。
Q:开了并行复制为什么还有延迟?
检查 binlog_format 是否是 ROW,slave_parallel_type 是否是 LOGICAL_CLOCK,以及事务之间是否存在冲突导致无法并行。大事务和 DDL 本身无法并行回放,仍需拆分或错峰。
Q:主从延迟突然飙升到很大,第一件事做什么?
先看 SHOW REPLICA STATUS 确认两个线程是否都 Running,再看从库 SHOW PROCESSLIST 有没有大事务或 DDL 在回放,同时检查从库的资源是否被打满。绝大多数突发延迟都能在这三步里定位。
Q:半同步能解决延迟吗?
不能。半同步保证的是日志送达,不保证回放完成。它解决的是主库崩溃丢数据的问题,与回放延迟是两回事。
Q:从库需要和主库一样的配置吗?
建议接近。从库规格明显低于主库时,回放跟不上几乎是必然的,尤其是磁盘和 CPU。可以略低于主库,但要留足回放余量。
Q:延迟高能把从库直接提升为主库吗?
不建议。延迟高的从库数据落后于主库,直接提升会丢失这部分数据。除非主库已彻底不可用,才考虑用最新进度的从库提升,并接受数据损失。
总结
- 主从延迟的本质是"从库回放速度赶不上主库写入速度",主库并发写、从库单线程回放是结构性原因。
- 排查先看
SHOW REPLICA STATUS,确认 IO/SQL 线程都是 Yes,区分是"网络拉取慢"还是"回放慢"。 Seconds_Behind_Master有局限,关键场景要用pt-heartbeat测真实延迟。- 头号原因是大事务和未开并行复制,优化优先级是:开并行复制、拆大事务、处理 DDL、用 ROW 格式、错峰读流量。
- 半同步解决的是丢数据问题,不能消除回放延迟,别混淆。
- 业务层用"读自己写走主库、容忍延迟走从库"兜底,同时必须监控复制线程状态和真实延迟,延迟与中断都要告警。
企业QQ咨询




