主从复制不是读写分离的自动开关
MySQL 主从复制本身只负责数据同步,不自带读写路由能力。应用层或中间件必须明确区分
SELECT和
INSERT/UPDATE/DELETE请求,否则所有流量仍打向主库,从库形同虚设。
常见错误是误以为开启
CHANGE MASTER TO后“自然就分开了”,结果监控里看到从库
Seconds_Behind_Master持续上涨,而主库压力纹丝不动——其实是没改应用代码或没配代理。 应用直连场景:需在业务代码中手动拆分连接池,写用
masterDataSource,读用
slaveDataSource使用
ProxySQL或
MaxScale时,必须配置
mysql_query_rules规则,匹配
SELECT才转发到
hostgroup 2(从库组) ORM 如 MyBatis + Spring,容易忽略
@Transactional(readOnly = true)不一定触发从库路由,得看具体
AbstractRoutingDataSource实现逻辑
从库延迟导致读不到最新数据
Seconds_Behind_Master显示为 0 并不等于强一致性。网络抖动、大事务回放、从库 IO 能力弱于主库,都会造成“伪实时”。尤其在订单创建后立刻查详情的场景,极易读到空结果。
这不是配置问题,是架构约束。解决思路不是压低延迟(极限也难到毫秒级),而是分类处理:
对刚写入就必须读的请求(如“提交订单→查订单号”),强制走主库,加/*+ FORCE_MASTER */注释或调用专用主库接口 报表类、用户中心等非关键读,容忍几秒延迟,直接走从库 避免在事务内混用主从连接——Spring 的
@Transactional默认绑定单个数据源,跨源事务会破坏 ACID
主从切换后读请求仍在旧主库上失败
传统 MHA 或 Orchestrator 完成故障转移后,新主库 IP/端口变了,但客户端连接池还缓存着旧地址。此时
SELECT报错
ERROR 2003 (HY000): Can't connect to MySQL server或持续超时。
根本原因在于连接池无感知。应对方式取决于架构层级:
应用侧:用支持动态重连的驱动(如 MySQL Connector/J 8.0+ 的autoReconnect=true&failOverReadOnly=false),但仅缓解,不根治 中间件侧:ProxySQL 支持
mysql_servers表热更新,配合 Orchestrator 的回调脚本可自动刷新后端列表 DNS 层:将
master.db解析指向 VIP,VIP 漂移到新主库,要求应用全用域名连接且禁用 DNS 缓存(
connectTimeout=3000配合
cachePrepStmts=false)
半同步复制不能代替高可用决策
启用
rpl_semi_sync_master_enabled=ON只保证至少一个从库收到 binlog,不保证它已执行完。当主库崩溃,那个“已接收”的从库可能
Exec_Master_Log_Pos还卡在中间,强行提升会导致数据丢失。
真正决定能否切换的是 GTID 或位点比对:
用SHOW SLAVE STATUS\G对比
Retrieved_Gtid_Set和
Executed_Gtid_Set,差集为空才说明完全追平 若用传统 binlog 文件+位置,需确认
Master_Log_File == Relay_Master_Log_File && Read_Master_Log_Pos == Exec_Master_Log_PosMHA 的
--check_repl_delay默认关掉,否则可能因短暂延迟拒绝切换——这在高并发写入时很常见
主从复制的可靠性边界很清晰:它解决单点故障,不解决脑裂、不保证实时、不自动修复应用路由。把“能切”当成“已切好”,是线上事故最常踩的坑。
