主题
概念卡片: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) | 需客户端配合获取 GTID | 5.7+ 且开 GTID |
结论:没有完美方案。 强制走主库是兜底且最常用;要读从库又要求一致性,等 GTID(配超时切主库)是最优雅解法。
判断主备无延迟的三种精度
| 方法 | 判断依据 | 精度 |
|---|---|---|
seconds_behind_master | 延迟秒数是否 =0 | 粗(秒级) |
| 对比位点 | Master_Log_File/Read_Master_Log_Pos vs Relay_Master_Log_File/Exec_Master_Log_Pos | 较准 |
| 对比 GTID | Retrieved_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 才回客户端。
常见误解(避坑)
- ❌ "读写分离的查询走从库就行,没坑"。→ 主从延迟会导致刚写完查从库读到旧数据(过期读)。
- ❌ "semi-sync 能保证所有从库数据一致"。→ 一主多从时主库只要一个从库 ack 就返回,其他从库可能没收到。
- ❌ "判断 seconds_behind_master=0 就绝对无延迟"。→ 有「binlog 未发出」的漏洞,要用等位点/等 GTID 才精确。
- ❌ "过期读一定要用最复杂的方案"。→ 大多数业务「关键查询强制走主库」就够,复杂方案留给高一致性要求场景。
关联
- 原始资料:28|读写分离有哪些坑?一主多从架构下,如何解决读写分离带来的“过期读”问题?
- 总览:MySQL技术栈总览
- 相关卡:概念卡片:MySQL主从复制与binlog(延迟的根源)
- 域地图:A00-百科/数据与存储/数据与存储