主从延迟压不到零。并行复制治标不治本,超阈值就强制走主库,或者承认异步复制不适合你。Seconds_Behind_Master 也别全信。
面试官问的是延迟,考的是你能不能把手段挂回链路上
答不好这题,通常不是不知道优化手段,而是说不出这些手段作用在复制的哪一环。链路都讲不清,优化就只能靠背。
五个步骤得能顺口说出来:
记住一点:第 4 到第 7 步之间,是从库自己在消化,延迟主要就长在这一段。你说的每一个优化手段,都得能指回这条链路的某一环。
延迟的两笔账,分开算
第一笔是网络。跨机房、跨地域的 RTT 从几毫秒到几十毫秒,物理距离决定的,能优化的只有带宽和机房规划。
第二笔是回放。主库 8 核并发写,从库默认只有一个 SQL 线程串行回放。主库写得越快,从库追得越吃力。
所以延迟的本质就一句话:主库的并发写入能力和从库的串行回放速度之间的差。所有优化,要么缩小这个差,要么绕开这个差。
第一层:能压的先压
大事务不拆,别的优化都是白费
一个大事务在 binlog 里就是一个巨大事件,从库 SQL 线程要一口气回放完,期间后面的事务全部排队。主库执行 3 秒,从库可能要卡 30 秒。
sql
-- 反例 一条语句删 200 万行
DELETE FROM orders WHERE create_time < '2024-01-01';
sql
-- 正解 每批 5000 行 循环提交
DELETE FROM orders WHERE create_time < '2024-01-01' LIMIT 5000;
大事务的坑不止延迟:主库 binlog 暴涨、从库 relay log 堆积、回放失败时重试代价极高。能拆就拆,这是性价比最高的手段。
并行复制:版本不一样,答案就不一样
视频重点提了这个,但版本差异必须讲清楚,否则面试官一追问就露馅。
ini
# my.cnf on slave
slave_parallel_workers = 8
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = ON
- 5.6 的 DATABASE 模式:按库并行,同一个库的事务还是串行。库少的业务基本没用。
- 5.7 的 LOGICAL_CLOCK:基于主库组提交,同一组内提交的事务可以并行回放,这是质变。
- 8.0 的 WRITESET:基于行级冲突检测,只要事务间没有写冲突就能并行,几乎榨干从库多核。
slave_preserve_commit_order=ON 别漏,它保证从库提交顺序和主库一致。关了确实更快,但可能读到中间状态。
换 SSD,最朴素的一招
机械盘换 SSD,随机写 IOPS 差一到两个数量级,回放速度直接翻倍。没有花活,但有效。
一主多从解决不了回放延迟
这里有个高频误区:一主多从解决的是"读请求压力",不是"回放延迟"。
每个从库都要完整回放同一份 binlog 流,加机器不会让单个从库追得更快。视频里说的"分摊从库压力",指的是把查询流量摊开,不是让延迟变小。这个区别能主动说出来,直接加分。
第二层:延迟消不掉,就在读路径兜底
压不到零,那就接受它,然后在读路径上兜底。
强制走主库
写后立刻读的场景------下单后查订单、支付后查余额------直接路由到主库。中间件规则或者 SQL hint 都行。代价是主库读压力上升,所以只对强一致要求的那几条链路开。
轮询等待从库追平
视频里提到的做法:查 Seconds_Behind_Master,大于阈值就 sleep,再查。
sql
SHOW SLAVE STATUS\G
-- Slave_IO_Running: Yes
-- Slave_SQL_Running: Yes
-- Seconds_Behind_Master: 3
-- Read_Master_Log_Pos: 1207
-- Exec_Master_Log_Pos: 4823
java
// 等待从库延迟收敛,超时则降级走主库
public boolean waitForSlave(long timeoutMs, long thresholdSec) {
long deadline = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < deadline) {
Long delay = querySecondsBehindMaster(); // 结果本地缓存 100ms,别每个请求都查
if (delay != null && delay <= thresholdSec) {
return true;
}
LockSupport.parkNanos(100_000_000L); // 100ms 重试
}
return false;
}
注意这段代码的代价:探针本身也有开销,QPS 上万时别每个读请求查一次,缓存一下。
Seconds_Behind_Master 到底能不能信
不能。它算的是从库 SQL 线程当前回放事件的 timestamp 与从库当前时间的差,有三个坑:
- IO 线程积压但 SQL 线程刚好回放到新事件时,它可能显示 0,实际 relay log 堆了一大堆。
- 主库长时间无写入,SQL 线程空转,它显示 0 或 NULL,不代表数据同步完成。
- 想精确判断,看
Read_Master_Log_Pos - Exec_Master_Log_Pos的位点差,或者performance_schema.replication_applier_status_by_worker。
它是一个粗略水位线,不是延迟的精确度量。 面试里主动说出这点,比多背两个参数管用。
第三层:要强一致,就别用异步复制
这是视频最后给出的态度,也最容易被忽略:主从复制的场景无法避免同步延迟,如果一定要强一致,就该考虑其他技术方案。
| 方案 | 一致性 | 代价 |
|---|---|---|
| 异步复制 | 最终一致 | 延迟不可避免 |
| 半同步复制 | 至少一个从库收到 binlog | 每次提交多一个 RTT,TPS 下降 |
| MGR | 多数派确认 | 部署复杂,网络要求高 |
| 分布式数据库 | 线性一致 | 换底座 |
半同步只保证"从库收到了 binlog",不保证已经回放完,写后读仍然可能读到旧数据。
面试追问链
Q:并行复制会不会导致主从数据不一致? A:开了 slave_preserve_commit_order 就不会。并行只影响回放阶段的调度,提交顺序仍与主库一致。
Q:slave_parallel_workers 开多大? A:一般不超过从库 CPU 核数,8 核从库开 8 到 16 比较常见。开太大反而因锁竞争变慢,得实测。
Q:半同步能彻底解决延迟吗? A:不能。它解决的是"数据不丢",不是"延迟为零",只保证一个从库收到 binlog。
Q:一主多从能解决回放延迟吗? A:不能,只能分摊读流量。每个从库都独立回放全量 binlog。
写在最后
异步复制下的主从延迟不是 bug,是设计。你能做的是把它压到业务能忍的量级,压不下去就换方案------而不是继续加从库。
真正难的不是记住这些手段,而是判断"延迟多少算超标"------1 秒?200 毫秒?这个阈值得由业务定,不是由 DBA 定。
你们线上怎么处理写后读?强制打主库,还是等延迟水位?评论区聊聊。
有用的话点个赞。