mysql主从切换前必须验证4个状态点:io和sql线程运行正常、seconds_behind_master稳定为0达60秒、master_pos_wait返回0、flush tables with read lock后主从count(*)一致。

主从同步前必须确认的 4 个状态点
MySQL 主从迁移能否“不停机”,核心不在切换动作本身,而在于切换前主从数据是否真正一致、延迟是否可控。很多人卡在 Seconds_Behind_Master 显示为 0 就认为可以切,结果应用一写就丢数据——这往往是因为 Seconds_Behind_Master 在并行复制下可能“假归零”。
实操中必须逐项验证:
-
SHOW SLAVE STATUS\G中Slave_IO_Running和Slave_SQL_Running都为Yes(仅看状态不看延迟) -
Seconds_Behind_Master稳定为0至少 60 秒(避免瞬时追平后又积压) - 对比主从的
SELECT MASTER_POS_WAIT('xxx', yyy, 10)返回值是否为 0(强制等待指定 binlog 位置追上) - 主库执行
FLUSH TABLES WITH READ LOCK后,立刻查从库同表的SELECT COUNT(*),确认无差异(锁只持几秒,用于最终一致性快照)
如何安全停写旧主并激活新主(含 GTID 关键操作)
停写不是简单关应用,而是分阶段阻断写入路径,并确保最后一批事务完整落库。GTID 模式下最关键的是避免 SET GLOBAL read_only = ON 后仍被绕过写入(比如 SUPER 权限用户或触发器)。
标准操作顺序如下:
- 在旧主执行:
FLUSH TABLES WITH READ LOCK(全局读锁,阻断所有写) - 立即记录当前位点:
SELECT @@global.gtid_executed;(GTID 模式下这是唯一可靠标记) - 在从库执行:
STOP SLAVE;→SELECT WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS('xxx', 10);(等待该 GTID 集合执行完毕) - 确认无误后,在从库执行:
SET GLOBAL read_only = OFF;+RESET SLAVE ALL;(清除从库身份,准备升主) - 旧主保持
read_only = ON,但不关库——留作故障回切兜底
应用连接切换时最容易被忽略的 DNS 缓存和连接池问题
数据库 IP 切换完成,不代表应用立刻连上新主。Java 应用常见问题是 HikariCP 连接池缓存了旧连接,Tomcat 的 JNDI 数据源未刷新,甚至客户端本地 /etc/hosts 或 DNS 解析 TTL 导致请求仍打到旧主。
必须同步处理:
- 应用侧:调用连接池的
evict()或重启应用(推荐先setReadOnly(true)再close()所有活跃连接) - DNS 层:提前将域名 TTL 设为 30 秒;切换时
dig +short your-db.example.com确认解析已更新 - 中间件层:若用了 ProxySQL 或 MaxScale,需手动执行
SAVE TO CONFIG并 reload 规则 - 监控补丁:在应用日志中 grep
SQLException.*1290(read_only拒绝写入)可快速定位残留写请求
回滚失败时如何用 binlog 快速找回丢失数据
切换后发现新主数据异常,最危险的操作是直接 START SLAVE 回退——因为旧主已 read_only,且 GTID 可能冲突。正确做法是把旧主当“只读数据源”,从它的 binlog 中提取缺失变更。
典型恢复流程:
- 在旧主查出误删/误改事务的起止位置:
mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000012 | grep -A 5 -B 5 "DELETE FROM users WHERE id = 123" - 用
mysqlbinlog --start-position=X --stop-position=Y截取对应 binlog 片段 - 过滤掉 DDL 和跨库语句:
sed '/^CREATE\|^ALTER\|^DROP/d' /tmp/patch.sql > /tmp/patch-safe.sql - 在新主上导入:
mysql -h new-master -u admin -p
这个过程依赖旧主 binlog 未被清理,所以切换前务必确认 expire_logs_days ≥ 7,且 sync_binlog = 1 开启。











