主从延迟排查与解决
问题
线上主从延迟持续增大,导致从库读到旧数据,如何排查和解决?
答案
一、主从复制原理
延迟发生在任何一个环节:
- 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 | 复制中断 |
三、延迟常见原因
| 原因 | 表现 | 解决方案 |
|---|---|---|
| 大事务 | 单个事务回放耗时几十秒~分钟 | 拆分大事务 |
| 大表 DDL | ALTER 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 > 10:WarningSeconds_Behind_Master > 60:CriticalSlave_IO_Running = No:Critical(复制中断)
常见面试问题
Q1: Seconds_Behind_Master 准确吗?
答案:
不完全准确,存在以下问题:
- 它是基于 SQL Thread 执行的最后一个事件的时间戳计算的
- 如果主库长时间没有写入,该值一直为 0(看起来无延迟)
- 大事务开始到提交期间,该值可能跳变
- 网络延迟导致 binlog 传输慢时,该值可能偏小
更准确的方案:专门写心跳表,定时写主库读从库比对时间。
Q2: GTID 复制相比传统复制有什么优势?
答案:
| 维度 | 传统复制 | GTID 复制 |
|---|---|---|
| 定位 | binlog 文件名 + 位置 | 全局唯一事务 ID |
| 主从切换 | 手动找位置 | 自动定位 |
| 漏检 | 可能重复或遗漏 | GTID 确保不重复 |
| 配置 | 复杂 | 相对简单 |
GTID 复制是 MySQL 5.6+ 推荐的复制方式。
Q3: 从库延迟 30 分钟以上怎么紧急处理?
答案:
- 确认原因:
SHOW SLAVE STATUS看是 IO 延迟还是 SQL 延迟 - IO 延迟:检查网络,确认 IO Thread Running
- SQL 延迟:
- 查看正在回放的 SQL(
SHOW PROCESSLIST找 SQL Thread) - 如果是大事务 DDL → 只能等待完成
- 如果是写入量大 → 临时增大
slave_parallel_workers
- 查看正在回放的 SQL(
- 业务降级:关键读请求暂时走主库
- 极端情况:重建从库(
mysqldump或xtrabackup)