中间件-04-MySQL读写分离后的验证方法
把 MySQL 读写分离上线后,最先完成的动作通常是:
- 应用接了新的数据源配置
- 中间件或代理层配置了主库和从库
- 业务压测时看到主库连接数下降
这些动作只能说明“读写分离可能已经接上”,但离“验证完成”还差很远。真实项目里更常见的问题是:
- 配置里写了读写分离,但部分读请求仍然打到主库
- 业务压测时主库压力下降了,从库延迟却开始积累
- 写后立刻读的场景偶发读到旧值,问题只在高峰期出现
- 主从切换演练看似成功,结果连接池、路由缓存和只读标记没有同步收敛
这说明读写分离验证的重点,从来不只是“有没有主从”,而是下面这几件事能不能被明确证明:
- 路由层:读请求和写请求是否真的按预期分流
- 复制层:从库是否稳定追上主库,延迟是否在业务可接受范围内
- 业务层:写后读、事务内读、强一致场景是否被误导到从库
- 故障层:从库延迟、从库只读失效、主从切换时,应用是否还能给出可解释的结果
这篇文章只讨论一个核心问题:
MySQL 读写分离上线后,应该怎样设计验证路径、执行骨架和排查方法,才能真正证明它在业务场景里可用,而不只是“配置看起来正确”。
一、先把验证目标收清,不要一上来就压流量
读写分离验证最容易跑偏的地方,是一开始就只做压测,看主库和从库负载曲线有没有变化。
更合适的顺序通常是先把目标拆成四组:
1. 路由正确性
需要回答的是:
- 普通查询是否稳定走从库
- 插入、更新、删除是否稳定走主库
- 显式事务中的查询会不会仍然被路由到从库
- 带锁查询、强一致查询、写后立即读是否被错误分流
如果这一层没有证明清楚,后面所有性能数据都没有意义。
2. 复制可追赶性
需要确认:
- 从库复制线程是否稳定
- 高峰期延迟是否会持续累积
- 大事务、批量更新、DDL 是否会让复制链路抖动
- 延迟恢复速度是否符合业务容忍时间窗
3. 业务一致性边界
并不是所有读请求都适合读写分离。更需要验证的是:
- 哪些接口允许“最终一致”
- 哪些接口必须“写后强一致”
- 哪些页面虽然是读接口,但结果直接影响后续业务动作
如果只按 SQL 类型分流,很容易把业务边界做错。
4. 切换与异常恢复
至少要验证下面几种场景:
- 单个从库延迟升高
- 单个从库不可用
- 主从切换后旧连接是否还指向错误实例
- 故障恢复后读流量是否能重新均衡
二、验证路径应该怎么走,才不会把问题混在一起
更适合现场执行的方式,不是一次把所有场景跑满,而是按下面 5 个阶段逐层推进:
1. 阶段一:验证实例角色和只读状态
先确认最基础的事实:
- 当前哪台是主库,哪几台是从库
- 从库
read_only、super_read_only是否符合预期 - 复制链路是否正常
- 数据源配置和代理层配置是否和实际实例一致
这一阶段如果就有偏差,后面不需要继续压流量。
常见检查动作包括:
1 | SHOW MASTER STATUS; |
如果项目还在用旧版本语法,也可以使用 SHOW SLAVE STATUS\G,但更重要的是把这些信息收进验证记录表,而不是只在终端里看一眼。
2. 阶段二:验证路由是否按规则生效
这一步的目标不是测性能,而是证明“谁在读、谁在写”。
更实用的做法是准备 3 组最小请求:
- 纯读请求:列表查询、详情查询、搜索查询
- 纯写请求:新增、更新、删除
- 写后立即读请求:提交后立刻查询结果
执行时同步观察:
- 主库连接数和执行 SQL
- 从库连接数和执行 SQL
- 代理层路由日志或应用数据源日志
如果业务链路里没有直接观察入口,至少要在数据库层打开慢 SQL、general log 或代理访问日志中的一种证据来源,避免后面只能靠猜。
3. 阶段三:验证复制延迟对业务有没有实际影响
这一阶段要从“数据库延迟”翻译成“业务影响”。
更值得验证的不是单纯看 Seconds_Behind_Master,而是把它放进业务动作里:
- 下单后立刻查订单
- 提交配置后立刻刷新详情页
- 更新权限后立刻访问受限资源
如果这些场景在复制延迟增加时开始出现旧值,就说明业务并不适合默认读从库。
4. 阶段四:验证高峰期读写分离是否仍然稳定
更接近真实现场的方式是混合流量验证:
- 读多写少流量
- 突发写入流量
- 批量更新或大事务
- 低频但关键的强一致查询
这一阶段重点看的是:
- 主库 QPS 是否下降但并未失真
- 从库 QPS、线程状态、延迟是否稳定
- 应用是否开始出现偶发旧读、重试、超时
5. 阶段五:验证故障和切换边界
读写分离最容易在这里暴露真实问题:
- 从库延迟持续升高时,读流量是否还继续打过去
- 从库摘除后,路由是否自动收敛
- 主从切换后,应用长连接是否刷新
- 切换后只读标志、VIP、代理缓存是否一致
三、最小执行骨架:一套可以直接落地的验证方式
如果需要把读写分离验证做成固定动作,至少应该准备下面几类内容。
1. 场景清单
建议先按业务一致性要求分类,而不是按表分类:
- 最终一致可接受场景
- 写后必须立即可见场景
- 事务内强一致场景
- 权限、库存、金额、状态流转等高风险场景
这一步的目的,是先决定哪些请求可以读从库,哪些必须固定走主库。
2. 验证脚本或请求集合
至少拆成下面 4 组:
read_only_cases:纯查询接口write_only_cases:纯写接口write_then_read_cases:写后立即读switch_cases:从库摘除、恢复、切换验证
如果是接口自动化体系,最好把数据准备、执行、证据采集、结果比对分开,避免一个脚本既造数据又校验延迟,最后问题无法定位。
3. 观察项
一次完整执行里至少应该同时记录:
- 主库和从库角色信息
- 主库与从库执行的 SQL 类型
- 复制状态和延迟
- 应用日志中的路由信息
- 请求结果与业务页面表现
更推荐做一张最小记录表:
| 场景 | 请求类型 | 预期路由 | 实际路由 | 复制延迟 | 业务结果 | 结论 |
|---|---|---|---|---|---|---|
| 订单列表 | 读 | 从库 | 从库 | 0s | 正常 | 通过 |
| 提交订单后查详情 | 写后读 | 主库或强一致读 | 从库 | 3s | 读到旧值 | 不通过 |
4. 故障注入动作
如果环境允许,至少准备下面几种可控动作:
- 限速或阻塞从库复制
- 制造批量更新,观察复制追赶
- 人工摘除单个从库
- 演练主从切换
没有故障注入,验证结果通常只能停留在“平时看起来没问题”。
四、验证时最容易遗漏的 5 个重点
1. 只验证了数据库,没有验证应用侧路由缓存
很多系统在连接池、ORM、中间件代理层都做了缓存,主从角色变更后,应用未必立刻感知。
结果往往是:
- 数据库已经切换成功
- 应用日志里仍然连旧实例
- 少量请求持续读旧从库
2. 只看复制延迟,没有看写后读场景
Seconds_Behind_Master 为 0 并不代表所有请求都安全,因为:
- 某些场景依赖更细粒度的事务提交可见性
- 代理层可能已经把请求发到错误节点
- 业务链路可能还依赖缓存或异步消费结果
3. 只做单条 SQL 验证,没有做接口级验证
数据库层验证能证明复制链路正常,但业务真正受影响的通常是接口和页面。
更有效的方式,是把数据库证据和业务返回一起看:
- 数据库里已经写入成功
- 从库还没追上
- 页面接口提前读了从库
- 用户看到旧状态
这四件事连起来,问题才真正闭环。
4. 忽略大事务、批量更新和 DDL
很多环境平时验证都正常,一到版本发布就出问题,原因通常不是读写分离本身,而是:
- 批量补数拖慢复制
- 大事务占住 apply
- DDL 导致从库短时抖动
所以验证脚本里最好单独留一组“变更高峰”场景。
5. 切换只验证了存活,没有验证业务正确性
主从切换后看到服务恢复,只能说明链路重新连上,不能说明业务结果已经正确。
更完整的收口应该是:
- 连接恢复
- 路由恢复
- 复制恢复
- 写后读恢复
- 高风险接口恢复
五、排查顺序:发现旧读或路由异常时先查什么
读写分离问题最怕一上来就盯着一层看。更稳的排查顺序通常是下面这样:
1. 先确认业务场景是否允许最终一致
先回答:
- 这个接口本来是否允许从库读
- 是否属于写后立刻读
- 是否属于事务内读
如果业务边界一开始就划错,后面排查数据库细节也没有意义。
2. 再确认路由规则和实际路由结果
重点看:
- 应用是否显式标记了主库读
- 代理层是否命中了例外规则
- 长连接是否仍然连旧实例
3. 再看复制是否跟得上
需要同步确认:
- 复制线程是否正常
- 延迟是否突然升高
- 是否存在大事务、锁等待、磁盘或网络抖动
4. 最后看 SQL 和索引问题
有些“读写分离导致慢”的问题,根因并不是分离,而是:
- 从库上索引缺失
- 查询计划和主库不一致
- 长查询把从库拖慢后继续放大延迟
六、真实案例:列表页偶发显示旧状态,根因并不是页面缓存
1. 场景
某业务系统上线读写分离后,工单状态更新接口压测通过,功能测试也能正常提交。但在联调阶段,列表页偶发显示旧状态,刷新几秒后才恢复。
2. 执行
现场先做了三组动作:
- 调用“更新工单状态”接口
- 立即调用“工单详情”和“工单列表”接口
- 同时记录应用日志、代理层路由日志、主从复制状态
3. 现象
现象表现很一致:
- 更新接口返回成功
- 详情接口大多数时候正确
- 列表接口在高峰期偶发读到旧值
- 页面刷新 2 到 5 秒后恢复
4. 排查
按顺序排查后发现:
- 更新请求稳定走主库,没有写丢
- 详情接口命中了“强一致读主库”规则
- 列表接口默认走从库
- 高峰期从库
Seconds_Behind_Master升到 2 到 4 秒 - 同一时间窗口里有批量状态更新任务在跑,复制 apply 明显变慢
也就是说,问题并不是页面缓存,而是“列表接口本身被定义为普通读接口,但实际承担了写后立即确认状态的业务语义”。
5. 修复
修复动作分成两部分:
- 调整接口策略:状态更新后的关键确认链路固定走主库,列表接口增加短时间强一致兜底
- 调整验证基线:把“批量更新 + 写后读”纳入固定回归场景,复制延迟超过阈值时阻断默认从库读
修复后再次执行同样场景:
- 更新后确认链路不再读旧值
- 高峰期列表页结果稳定
- 从库延迟仍会波动,但不再直接暴露到关键业务动作
七、这篇文章真正想落下来的结论
MySQL 读写分离后的验证,核心不是证明“架构已经搭好”,而是证明下面三件事已经被控制住:
- 读写路由是可证明的,不是猜测的
- 复制延迟是可观测的,不是事后才发现的
- 业务一致性边界是明确的,不是默认所有读都能走从库
如果只做连接验证、压测验证和角色验证,读写分离大概率只能停留在“平时没事”。真正更有价值的验证方式,是把业务场景、路由证据、复制状态和故障演练收成一条完整链路。只有这样,后面出现旧读、切换异常或结果不一致时,才能快速判断问题到底出在规则设计、复制链路还是业务边界本身。