必须确认seconds_behind_master为0且持续30秒,检查read_master_log_pos与exec_master_log_pos相等,dns ttl≤30s,禁用中间件读负载均衡,清理应用层僵尸连接,使用私有dns并验证写入读取一致性。

MySQL主从延迟大时切DNS会丢数据
DNS切换本身不保证事务一致性,只要从库还没追上主库,切过去立刻读到旧数据,写请求还可能被路由到旧主库导致双写冲突。关键不是DNS快不快,而是主从同步是否真正就绪。
- 切DNS前必须确认
Seconds_Behind_Master为0,且持续稳定至少30秒(网络抖动可能导致瞬时归零) - 用
SHOW SLAVE STATUS\G检查Read_Master_Log_Pos和Exec_Master_Log_Pos是否相等,比Seconds_Behind_Master更可靠 - DNS TTL 不能设太高(建议 ≤ 30s),否则客户端缓存未刷新,部分流量仍打到旧地址
- 避免在业务高峰或大事务执行中触发切换——
ALTER TABLE或批量导入期间主从延迟极易飙升
读写分离中间件在迁移中反而成瓶颈
很多团队依赖 ShardingSphere 或 MyCat 做读写分离,但迁移时若配置没动态更新,新主库写入后,中间件仍把读请求发给旧从库,造成脏读;更糟的是,某些版本的中间件对主库故障转移支持不完整,会卡住连接。
- 迁移前确认中间件支持
auto-reconnect和master-switch事件回调,否则需手动 reload 配置 - 禁用中间件的“读负载均衡”功能,切流期间强制所有读走新主库(哪怕暂时牺牲读性能),避免跨库不一致
-
maxReplicationLag类参数(如mysql-router的max_replica_lag)必须设为0,否则它会把延迟大的新从库也纳入读池
应用层连接池没清理干净导致连错实例
Java 应用常用 HikariCP,它的连接池默认复用已有连接;Go 的 database/sql 也有类似行为。DNS 切了,但老连接还在用旧 IP,尤其当连接空闲超时时间(idleTimeout)设得过大(比如 30 分钟),这些“僵尸连接”会在后续请求中继续往旧库发 SQL。
- 切 DNS 前主动调用
hikariDataSource.evictConnections()或重启应用(最稳妥) - 设置
connectionTimeout=3s、idleTimeout=60s,避免连接长期滞留 - 在数据库端用
KILL命令清掉旧 IP 的活跃连接(查SELECT * FROM information_schema.PROCESSLIST WHERE HOST LIKE 'old-ip%')
为什么不能跳过从库直接切主库IP?
直接改应用配置里的数据库地址看似简单,但风险极高:不同服务部署节奏不一致,有的改了有的没改,瞬间出现多点写入;容器化环境里,ConfigMap 或 Secret 更新有延迟,K8s livenessProbe 可能因连不上旧库误杀 Pod。
- DNS 是唯一能原子控制全量流量入口的手段,但前提是 DNS 解析路径可控(避开 CDN、LocalDNS 缓存)
- 生产环境务必用私有 DNS(如 CoreDNS)而非公共 DNS,确保
TTL和cache行为可预期 - 切完立即验证:在新主库执行
INSERT INTO test_seq (ts) VALUES (NOW()),再从所有读端查这条记录是否存在
真正的难点不在切的那一刻,而在如何证明“此刻所有写已落盘、所有读已对齐”。监控看板上的数字和命令行里的 0,都只是间接证据。











