主从切换时连接池仍向旧主库发请求会导致写入失败或静默丢失;需启用连接池验证查询、主动清理连接、配合HAProxy健康检查与动态配置更新,并确保GTID同步一致及读写流量精准路由。

主从切换时连接池还在往旧主库发请求怎么办
负载均衡器(如 HAProxy、Nginx)本身不感知 MySQL 主从角色变化,它只按配置转发流量。一旦你手动或自动把原主库切为从库,但连接池没清空,应用仍会通过旧连接往已降级的实例写入,直接报错 ERROR 1290 (HY000): The MySQL server is running with the --read-only option 或更糟——写入静默失败。
实操建议:
- 应用侧必须启用连接池的「验证查询」机制:比如 HikariCP 的
connection-test-query=SELECT 1+validation-timeout,确保每次借连接前执行一次轻量查询;MySQL 8.0+ 推荐用connection-init-sql=SELECT 1配合auto-commit=true避免事务残留 - 禁止复用长连接池中已建立的连接:切换前主动调用
dataSource.evictAllConnections()(Druid)或HikariDataSource.close()后重建(需配合应用重启或热重载能力) - HAProxy 需开启
option mysql-check user haproxy_check,并在后端 MySQL 上创建专用校验用户,否则健康检查永远返回 UP,根本发现不了主从倒换
HAProxy 如何识别哪个后端是当前主库
HAProxy 本身没有内置 MySQL 主从状态探测逻辑,它只能靠外部信号驱动配置更新。硬编码 server mysql-master 10.0.1.10:3306 check 这种写法在切换后立刻失效。
实操建议:
- 用 Consul + consul-template 或 etcd + confd 实现配置自动渲染:当 MHA / Orchestrator / 自研脚本检测到主库变更,把新主库 IP 写入 KV 存储,模板自动刷新 HAProxy 的
server行并触发haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid) - 避免用 DNS 轮询做服务发现:DNS TTL 缓存导致切换延迟不可控,
dig +short mysql-primary.example.com返回多个 IP 就等于没做主从识别 - 如果用 ProxySQL,直接走
mysql_servers表 +mysql_replication_hostgroups配置,它原生支持基于Read_Hostgroup/Write_Hostgroup的路由,但要注意monitor_enabled=1和check_interval_ms=1000必须开启
从库延迟大时强制切主会不会丢数据
会。只要从库 Seconds_Behind_Master > 0,且你没做 GTID 或半同步复制,切主瞬间未同步的 binlog 就永久丢失。线上常见错误是监控只看 Slave_IO_Running 和 Slave_SQL_Running 都为 Yes,就认为可以切。
实操建议:
- 切换前必须查
SHOW SLAVE STATUS\G中的Retrieved_Gtid_Set和Executed_Gtid_Set是否一致;非 GTID 环境则比对Master_Log_File == Relay_Master_Log_File且Exec_Master_Log_Pos == Read_Master_Log_Pos - 半同步不是万能的:
rpl_semi_sync_master_wait_point=AFTER_SYNC才保证主库提交前等从库落盘;若配成AFTER_COMMIT,主库 crash 仍可能丢最后一批事务 - 用 pt-heartbeat 持续监控真实延迟:它写时间戳到主库,从库回读对比,比
Seconds_Behind_Master更准——后者在从库 SQL 线程卡住时会归零误导人
应用层怎么区分读写流量才不被负载均衡器绕过
很多团队以为只要 HAProxy 把 3306 端口分给两个 backend 就算读写分离了,结果发现所有请求都打到了写组——因为 JDBC URL 里没显式指定 useServerPrepStmts=false&rewriteBatchedStatements=true 之类参数,驱动内部把读请求也转成了写协议。
实操建议:
- 必须用带读写分离能力的客户端:ShardingSphere-JDBC 的
master-slave规则、MyCat 的schema.xml中<dataHost>的writeType和switchType,或者直连 ProxySQL 并设置mysql-users表中的default_hostgroup - JDBC URL 中禁用隐式写操作:
jdbc:mysql://proxy:3306/db?allowMultiQueries=false&autoReconnect=true&failOverReadOnly=false—— 注意failOverReadOnly=false是关键,否则主库挂掉会自动切到只读从库还继续发写请求 - 不要依赖注释做路由:
/*+ db_type:write */ UPDATE ...这类写法在 PreparedStatement 场景下会被预编译剥离,实际走到从库上执行失败
真正难的不是切,是确认从库追平那一刻的毫秒级窗口、连接池里最后一个脏连接的生命周期、以及 ProxySQL 规则加载和缓存失效之间那几十毫秒的竞态。这些地方一松懈,无感知就变成“感知特别强”。


















