为 HAOVP 整理多节点部署方案时,我把 MySQL 主库和从库放进了节点拓扑。主库承担业务写入,从库通过复制保存数据副本。
但“有主有从”只能说明复制关系存在。它不自动等于读写分离,更不代表主库不可用时,系统已经能够安全地自动切换。要说清数据库的可用性,需要把复制、流量路由、主库提升和应用恢复分开看。
主从复制解决什么
一组简化的复制关系可以表示为:
业务写入 -> 主库 -> binary log -> 从库读取并重放
-> 另一个从库读取并重放主库记录数据变更,从库获取并执行这些变更。这样可以得到额外的数据副本,也为只读查询、备份和故障恢复提供基础。
MySQL 默认的复制方式是异步复制。主库提交事务后,不需要等待从库完成接收和重放。因此,主库已经提交的数据在某个时刻可能还没有到达从库。
这意味着复制延迟不能忽略:
主库:订单状态已经更新
从库:仍然返回更新前的状态这个例子只说明一致性风险,不对应 HAOVP 的真实业务数据。
半同步复制可以让主库在提交路径上等待至少一个从库确认收到事务事件,但“已经收到”仍不等于“所有从库都已经执行完成”。它改善部分数据丢失风险,也会增加提交等待,仍不能单独完成故障切换。
有从库不等于读写分离
读写分离需要决定每一类 SQL 应该发送到哪个数据库实例。
INSERT / UPDATE / DELETE -> 主库
普通 SELECT -> 从库
必须读到最新结果的 SELECT -> 主库这个路由可以由应用数据源、数据库中间件或代理层处理。只有建立并验证了路由规则,才能说系统使用了读写分离。
如果所有业务服务仍连接主库,那么即使从库一直同步,也只是保留副本,并没有分担查询流量。HAOVP 当前部署文档写明业务写入以主库为准,但没有足够证据证明读请求已经稳定路由到从库,所以我不能把当前拓扑写成“已经实现读写分离”。
读写分离首先要处理复制延迟
把所有查询直接送到从库,会出现“刚写完却读不到”的情况。常见场景包括:
用户提交修改
-> 主库事务提交
-> 页面立即查询详情
-> 查询被路由到尚未追平的从库
-> 页面显示旧状态可以按业务语义选择策略:
- 写入后的短时间内固定读取主库;
- 对一致性要求高的查询始终读取主库;
- 允许最终一致的列表、统计或推荐查询读取从库;
- 检测复制延迟,超过阈值时停止向该从库分配查询;
- 使用能够标识复制进度的机制,等待从库追到需要的位置。
不能只按 SQL 是否以 SELECT 开头机械分流。事务中的查询、锁定读、依赖刚写入结果的查询,都可能需要访问主库。
主库故障后,手工切换要做什么
主库不可用时,可以选择一个数据较新的从库提升为新主库,但这不是简单修改一个连接地址。
一次受控的手工切换至少需要考虑:
确认旧主库不能继续接受写入
-> 比较从库复制进度
-> 选择并提升新主库
-> 让其他从库改为复制新主库
-> 更新数据库访问入口
-> 验证应用读写
-> 处理旧主库恢复后的身份这里最危险的是双主写入。如果旧主库只是网络隔离而不是彻底停止,它可能仍在接受部分应用写入;此时直接提升另一个节点,会形成两个写入源。两边数据继续变化后,很难依靠普通复制自动合并。
因此,切换前要有可靠的隔离措施,也就是先确保旧主库失去写入资格,再提升新主库。
真正的自动故障切换多了哪些能力
自动故障切换需要一套协同机制,而不是一条“自动重启”配置。
它至少包含:
- 持续检测主库是否真正不可用;
- 区分主库故障、网络分区和检测节点自身故障;
- 比较候选从库的复制进度;
- 防止旧主库继续写入;
- 提升合适的从库;
- 重建其他从库的复制关系;
- 更新代理、虚拟入口或服务发现中的主库位置;
- 让应用连接池丢弃旧连接并连接新主库;
- 记录切换过程并发出告警;
- 在原主库恢复后以受控方式重新加入。
可以把它看成一条完整链路:
故障检测
-> 仲裁与隔离
-> 选主
-> 拓扑重建
-> 流量迁移
-> 应用恢复
-> 数据核对任何一段缺失,都可能出现“数据库已经提升,但业务仍然不可用”,或者“业务恢复了,但部分事务丢失或重复执行”。
应用连接也是故障切换的一部分
假设数据库层已经选出新主库,业务服务仍可能持有旧连接。连接池需要识别连接失效、清理旧连接,并通过稳定入口获得新的可写实例。
一个通用数据源配置只能表达连接入口:
spring:
datasource:
url: jdbc:mysql://db-write.example/database
username: ${DB_USER}
password: ${DB_PASSWORD}地址、库名和变量均为占位示例,不对应真实部署参数。
如果 db-write.example 背后没有代理、服务发现或可更新的解析机制,数据库发生主从提升后,这个地址不会自己指向新主库。即使入口能更新,还要验证 DNS 缓存、连接池重试、事务失败和连接超时等行为。
切换期间的事务怎么处理
主库故障发生在事务执行过程中时,应用可能只看到连接中断,无法仅凭异常判断事务是否提交。
应用发送 COMMIT
-> 主库完成提交
-> 返回结果前连接中断
-> 应用不知道提交是否成功如果客户端直接重试,可能产生重复写入。因此,数据库切换还需要业务层配合:
- 写接口使用幂等键或唯一约束;
- 明确哪些异常允许重试;
- 对结果不确定的事务进行状态查询或补偿;
- 消息消费和定时任务避免重复执行产生错误结果;
- 不把“连接恢复”当成“业务状态一定正确”。
高可用不是只有数据库组件参与,业务语义同样决定切换能否安全完成。
怎样验证,而不是只看配置
我会把测试分成正常复制、读写路由和故障切换三组。
复制验证
- 主库写入后,从库是否收到并正确重放;
- 复制线程、延迟和错误是否可观察;
- 从库重启后是否能继续追赶;
- 大事务或网络波动时延迟如何变化。
读写分离验证
- 写操作是否只到可写实例;
- 普通查询是否按规则进入从库;
- 写后立即读是否会看到旧数据;
- 从库延迟或不可用时,查询怎样回退;
- 事务内的读写是否保持在正确实例。
故障切换验证
- 主库进程停止、宿主机中断和网络隔离能否被正确区分;
- 候选节点是否拥有足够新的数据;
- 提升过程中旧主库是否被隔离;
- 应用多久恢复连接,失败请求如何处理;
- 其他从库能否跟随新主库;
- 原主库恢复后是否会错误地再次接受写入;
- 切换前后的数据是否一致。
只有测试过这些场景,才能描述恢复时间、数据丢失范围和当前方案的真实能力。拓扑图、容器状态和复制线程正常都不能替代故障演练。
HAOVP 当前能确认到哪一步
目前的多节点部署文档能够确认:MySQL 规划为一个主库和多个从库,业务写入仍以主库为准,部署后需要检查从库复制状态。
当前不能据此确认:
- 业务查询已经完成读写分离;
- 主库故障后从库会自动提升;
- 应用访问入口会自动迁移;
- 故障期间的事务已经过一致性验证;
- 数据库已经达到生产级高可用。
所以我把主从复制看作数据库冗余的基础,把读写分离看作查询路由能力,把手工切换看作运维恢复流程,把自动故障切换看作包含检测、隔离、选主、路由和应用恢复的完整系统。四者有关联,但不能相互替代。