跳到主要内容

主从延迟排查与解决

问题

线上主从延迟持续增大,导致从库读到旧数据,如何排查和解决?

答案

一、主从复制原理

延迟发生在任何一个环节:

  • IO 延迟:网络带宽不足,binlog 传输慢
  • SQL 延迟:从库回放速度跟不上主库写入速度

二、查看延迟

-- 在从库执行
SHOW SLAVE STATUS\G

-- 关键字段
-- Seconds_Behind_Master: 延迟秒数(0 表示无延迟)
-- Relay_Log_Space: Relay Log 大小
-- Exec_Master_Log_Pos vs Read_Master_Log_Pos: 执行位置 vs 接收位置
指标含义
Seconds_Behind_Master = 0无延迟
Seconds_Behind_Master > 0延迟(值越大越严重)
Seconds_Behind_Master = NULL复制中断

三、延迟常见原因

原因表现解决方案
大事务单个事务回放耗时几十秒~分钟拆分大事务
大表 DDLALTER TABLE 阻塞回放Online DDL / gh-ost
从库单线程回放SQL Thread 只有一个开启并行复制
从库负载高从库 CPU/IO 被查询占满增加从库,分散读流量
网络问题IO Thread 传输慢检查带宽、优化 binlog 格式
主库写入突增短时间大量写入流量削峰

四、解决方案

方案 1:开启并行复制(MySQL 5.7+)

-- 从库开启多线程回放
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8; -- 根据 CPU 核心数设置
SET GLOBAL slave_preserve_commit_order = ON; -- 保证提交顺序
并行策略说明效果
DATABASE不同库并行回放多库场景有效
LOGICAL_CLOCK主库同组提交的事务并行回放推荐,效果最好

方案 2:避免大事务

-- ❌ 一次删除 100 万行
DELETE FROM logs WHERE created_at < '2023-01-01';

-- ✅ 分批删除
DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 10000;
-- 循环执行直到删完,每批之间 sleep

方案 3:读写分离策略优化

// 关键读走主库,普通读走从库
class DBRouter {
// 写操作后标记:该用户短时间读主库
async write(userId: string, sql: string) {
await this.master.query(sql);
// 标记 3 秒内读主库
await redis.set(`route:master:${userId}`, '1', 'EX', 3);
}

async read(userId: string, sql: string) {
const forceMaster = await redis.get(`route:master:${userId}`);
if (forceMaster) {
return this.master.query(sql);
}
return this.slave.query(sql);
}
}

方案 4:半同步复制

-- 半同步:主库等待至少一个从库确认收到 binlog 后才返回
-- 确保从库至少接收了数据
-- 安装半同步插件
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = ON;
SET GLOBAL rpl_semi_sync_master_timeout = 1000; -- 1 秒超时降级为异步

五、监控告警

-- 监控延迟指标
-- Prometheus + mysqld_exporter 可采集以下指标:
-- mysql_slave_status_seconds_behind_master
-- mysql_slave_status_slave_io_running
-- mysql_slave_status_slave_sql_running

告警阈值建议:

  • Seconds_Behind_Master > 10Warning
  • Seconds_Behind_Master > 60Critical
  • Slave_IO_Running = NoCritical(复制中断)

常见面试问题

Q1: Seconds_Behind_Master 准确吗?

答案

不完全准确,存在以下问题:

  1. 它是基于 SQL Thread 执行的最后一个事件的时间戳计算的
  2. 如果主库长时间没有写入,该值一直为 0(看起来无延迟)
  3. 大事务开始到提交期间,该值可能跳变
  4. 网络延迟导致 binlog 传输慢时,该值可能偏小

更准确的方案:专门写心跳表,定时写主库读从库比对时间。

Q2: GTID 复制相比传统复制有什么优势?

答案

维度传统复制GTID 复制
定位binlog 文件名 + 位置全局唯一事务 ID
主从切换手动找位置自动定位
漏检可能重复或遗漏GTID 确保不重复
配置复杂相对简单

GTID 复制是 MySQL 5.6+ 推荐的复制方式。

Q3: 从库延迟 30 分钟以上怎么紧急处理?

答案

  1. 确认原因SHOW SLAVE STATUS 看是 IO 延迟还是 SQL 延迟
  2. IO 延迟:检查网络,确认 IO Thread Running
  3. SQL 延迟
    • 查看正在回放的 SQL(SHOW PROCESSLIST 找 SQL Thread)
    • 如果是大事务 DDL → 只能等待完成
    • 如果是写入量大 → 临时增大 slave_parallel_workers
  4. 业务降级:关键读请求暂时走主库
  5. 极端情况:重建从库(mysqldumpxtrabackup

相关链接