Skip to content

概念卡片:MySQL 读写分离与过期读

一句话机制

读写分离靠「写主库、读从库」分摊压力,但主从异步复制有延迟,刚写完就查从库可能读到旧数据(过期读)。解决过期读有 6 种方案,从「强制走主库」到「等 GTID」精确度逐步提升,本质是「在可靠性和主库减压之间权衡」。

两种读写分离架构

架构特点
客户端直连少一层 proxy 转发,性能好、排查简单;但客户端要感知主备切换/迁移
中间 proxy 层客户端无感知,proxy 管路由;但 proxy 自己也要高可用,整体复杂

趋势是往 proxy 方向(如 MyCat/ProxySQL/Atlas)。

过期读的 6 种解决方案(对比表)

方案优点缺点适用
1. 强制走主库简单、最可靠、0 过期读放弃这部分读写分离,主库压力大绝大多数业务首选(关键查询走主库)
2. Sleep简单不精确(睡少了读不到、睡多了浪费)实时性要求极低
3. 判断主备无延迟比 sleep 精确有漏洞:binlog 还没传到从库时,从库也认为「无延迟」配合 semi-sync
4. Semi-sync解决「binlog 未到达」误判一主多从时只能保证 1 个从库收到;有过度等待数据安全要求高
5. 等主库位点逻辑精确(只等特定事务)要先查主库位点(多一次交互);超时需退化业务要求高
6. 等 GTID省去查位点(客户端拿 GTID)需客户端配合获取 GTID5.7+ 且开 GTID

结论:没有完美方案。 强制走主库是兜底且最常用;要读从库又要求一致性,等 GTID(配超时切主库)是最优雅解法。

判断主备无延迟的三种精度

方法判断依据精度
seconds_behind_master延迟秒数是否 =0粗(秒级)
对比位点Master_Log_File/Read_Master_Log_Pos vs Relay_Master_Log_File/Exec_Master_Log_Pos较准
对比 GTIDRetrieved_Gtid_Set vs Executed_Gtid_Set最准

精确方案的核心命令

sql
-- 等主库位点(在从库执行,最多等 N 秒)
select master_pos_wait(file, pos, 1);
-- 返回 ≥0 整数 = 已执行到位点;-1 = 超时;NULL = 同步线程异常

-- 等 GTID(在从库执行)
select wait_for_executed_gtid_set(gtid_set, 1);
-- 返回 0 = 已包含该 GTID;1 = 超时

流程:更新事务提交 → 拿主库位点/GTID → 从库执行 wait 命令 → 返回成功则查从库,超时则退化到主库。

为什么「判断无延迟」还有漏洞

主备延迟的完整链路分三段:① 主库写 binlog 并回客户端 → ② binlog 传到备库 → ③ 备库执行完。判断「无延迟」只看②③,但可能卡在①——事务已回客户端、binlog 还没发出。这时从库认为没延迟,实际查不到这个事务 → 过期读。semi-sync 就是堵这个洞:主库等从库 ack 才回客户端。

常见误解(避坑)

  1. ❌ "读写分离的查询走从库就行,没坑"。→ 主从延迟会导致刚写完查从库读到旧数据(过期读)。
  2. ❌ "semi-sync 能保证所有从库数据一致"。→ 一主多从时主库只要一个从库 ack 就返回,其他从库可能没收到。
  3. ❌ "判断 seconds_behind_master=0 就绝对无延迟"。→ 有「binlog 未发出」的漏洞,要用等位点/等 GTID 才精确。
  4. ❌ "过期读一定要用最复杂的方案"。→ 大多数业务「关键查询强制走主库」就够,复杂方案留给高一致性要求场景。

关联

最近更新