在数据库高可用架构中,读写分离是提升系统吞吐量的核心手段。其基本逻辑是:主库(Master)负责所有写入操作,从库(Slave)分担查询压力。然而,由于主从同步存在物理延迟,客户端可能在备库读到尚未同步的旧数据,这种现象被称为"过期读" 。
一、 读写分离的实现架构
实现读写分离主要有两种主流方案:
- 客户端直连 (Client-side)
- 执行逻辑:应用代码内部实现负载均衡,配置主库和多个从库的 IP,根据请求类型分发。
- 特点:性能损耗极低,架构简单;但运维成本高,主备切换时需要修改所有客户端配置 。
- 带 Proxy 的中间件架构 (Proxy-side)
- 执行逻辑:应用只连接 Proxy,由 Proxy 负责 SQL 解析、路由分发和后端存活检测。
- 特点:对应用透明,运维方便;但增加了网络延迟,且 Proxy 自身需要考虑高可用 。
二、 解决"过期读"的六种方案
为了在提升性能的同时保证数据准确,需要针对不同业务场景选择合适的"过期读"解决方案。
- 强制走主库方案
- 策略:将查询请求分为"允许延迟"和"必须实时"两类。
- 示例:用户刚修改完个人资料,其"个人中心"页面必须强制查主库以保证可见性;而其他用户浏览该资料时可走备库。
- Sleep 方案
- 策略 :在写操作后,读操作前让客户端执行一次短时间的
sleep。 - 评价:这是一种不严谨的概率性方案。如果延迟超过 sleep 时间,依然会读到旧数据;反之则会浪费响应时间。
- 判断主备延迟方案
- 在查询备库前,先通过以下方式确认同步进度:
- 监控 SBM :确保
seconds_behind_master为 0。 - 对比位点 :确保已收到的日志位点
Read_Master_Log_Pos等于已执行位点Exec_Master_Log_Pos。 - 对比 GTID 集合 :确保备库已执行的集合
Executed_Gtid_Set包含已收到的集合Retrieved_Gtid_Set。
- 监控 SBM :确保
- 配合半同步复制 (Semi-Sync) 方案
- 策略:利用半同步复制保证主库返回成功前,至少有一个备库收到了 binlog。
- 配合:结合"对比位点"法。只要备库收到的位点等于主库最新的位点,代表数据已到。
- 局限:在一主多从架构下,如果请求落到了那个恰好没收到日志的备库,方案会失效 。
MASTER_POS_WAIT等位点方案
- 物理流程 :
- 写操作完成后,通过
SHOW MASTER STATUS获取主库当前的File和Position。 - 客户端去备库执行
SELECT MASTER_POS_WAIT(File, Position, timeout)。 - 如果函数返回 ≥0\ge 0≥0,说明备库已追平该点,可以安全读取。
- 写操作完成后,通过
WAIT_FOR_EXECUTED_GTID_SET等 GTID 方案
- 这是目前最先进且精确的方案,基于 MySQL 5.7+ 的 GTID 机制
- 物理流程 :
- 主库写入成功后返回该事务的
gtid给客户端。 - 客户端去备库执行
SELECT WAIT_FOR_EXECUTED_GTID_SET(gtid, timeout)。 - 备库会阻塞等待,直到该 GTID 被本地执行完成。
- 主库写入成功后返回该事务的
- 优化 :设置
session_track_gtids=OWN_GTID,主库会在响应报文里自动带上最新的 GTID,简化开发逻辑 。