“零停机”是伪命题,真正可控的最小停机取决于跳过Seconds_Behind_Master == 0陷阱;主从复制升级法通过前置风险验证、物理备份恢复、GTID自动位点对齐及4个状态点组合验证(IO/SQL线程运行、延迟稳零60秒、MASTER_POS_WAIT返回0、锁后COUNT一致),实现比原地升级更可控的切换;GTID下停写旧主需FLUSH加READ LOCK、记录gtid_executed、从库WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS、再SET read_only=OFF并RESET SLAVE ALL;应用连接切换须处理DNS缓存与连接池老化,切后须人工验证新主真实连接。

能压到秒级,但“零停机”是伪命题——真正可控的最小停机,取决于你是否跳过 Seconds_Behind_Master == 0 这个陷阱。
主从复制升级法为什么比原地升级更可控
小版本原地升级(如 5.7.30 → 5.7.40)看似快,但一旦新二进制文件与旧数据字典不兼容,mysqld 启动失败或崩溃,回滚成本远高于预期。而主从复制升级法把风险前置:新版本实例作为从库长期运行,兼容性、性能、SQL行为差异都在切换前暴露。
- 新从库必须用目标版本安装,不能复用旧版
mysqld+ 新配置 - 同步方式优先选物理备份恢复(
xtrabackup),比逻辑导入(mysqldump)快一个数量级,且保留原始页结构 - GTID 模式下,
CHANGE MASTER TO不需要手动指定MASTER_LOG_FILE和MASTER_LOG_POS,靠GTID_EXECUTED自动对齐,大幅降低位点错配风险
切换前必须验证的 4 个状态点,缺一不可
很多人在 SHOW SLAVE STATUS\G 里看到 Seconds_Behind_Master: 0 就切,结果应用一写就丢数据。并行复制下这个值可能“假归零”,必须组合验证:
-
Slave_IO_Running和Slave_SQL_Running都为Yes(只看状态,不看延迟) -
Seconds_Behind_Master稳定为0至少60秒(不是瞬时归零) - 在从库执行
SELECT MASTER_POS_WAIT('binlog.000001', 123456789, 10),返回值必须为0(强制等待指定位置追上) - 主库执行
FLUSH TABLES WITH READ LOCK后,立刻在从库查同表COUNT(*),结果必须完全一致(锁只持几秒,用于最终一致性快照)
GTID 模式下停写旧主的关键操作顺序
停写不是简单关应用,而是防止最后一批事务被绕过落库。GTID 下最危险的是 read_only = ON 被 SUPER 权限用户或触发器绕过:
- 旧主执行
FLUSH TABLES WITH READ LOCK(阻断所有写入) - 立刻记录当前 GTID 集合:
SELECT @@global.gtid_executed(这是唯一可靠标记) - 从库执行
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 切换完成,不代表应用立刻连上新主。连接池里的旧连接还在复用,DNS TTL 可能缓存旧地址数分钟:
- 提前将应用连接池的
maxLifetime设为30s以内,避免长连接滞留 - 若用 DNS 解析,切换前将 TTL 降至
30秒,并在切换后强制刷新(systemd-resolve --flush-caches或应用层 reload) - 中间件(如 ProxySQL、ShardingSphere)需预配置新主地址,并在切换时触发
reload或switchover命令,而非等 DNS 生效 - 切完立刻查新主
SHOW PROCESSLIST,确认有来自应用的真实连接,而不是残留的空闲连接
真正耗时的从来不是 SQL 执行或 binlog 同步,而是人对“一致”的误判、对缓存的遗忘、对权限边界的忽视。每个 SELECT、每个 FLUSH、每个 WAIT_UNTIL,都得亲手敲出来验证,不能依赖监控面板上那个绿色的 0。


















