登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

GTID 复制切换前要检查什么:一致性与故障回退清单

来源:17golang原创

时间:2026-10-08 09:58:47 153浏览 收藏

MySQL GTID 复制切换最容易出错的地方,不是执行哪条 CHANGE REPLICATION SOURCE TO,而是没有证明“候选副本已经包含旧主库在切换点提交的全部事务”。我的做法是先冻结写入,再用 gtid_executed 做集合比较,最后才提升候选副本。切换后如果新主库已经接收过写入,回退也必须重新比较 GTID,不能因为开了自动定位就直接切回。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/

要点速览
  • Seconds_Behind_Source 只能辅助观察延迟,不能单独证明数据一致。
  • 切换前要确认旧主库的 GTID 集合是候选副本已执行集合的子集,并排除候选副本独立写入。
  • 新主库一旦产生新 GTID,旧主库只有追上这些事务后才具备安全回退条件。

先冻结写入,再记录切换基线

我会先在业务层关闭写入口,或把写请求切到维护闸门;仅设置副本的只读状态并不能阻止旧主库继续产生新事务。随后分别记录旧主库和候选副本的复制状态:

-- 在旧主库记录切换点;source_set 只代表示例变量名
SELECT @@GLOBAL.gtid_executed AS source_set,
       @@GLOBAL.gtid_purged AS source_purged,
       @@GLOBAL.read_only AS read_only,
       @@GLOBAL.super_read_only AS super_read_only;

-- 在候选副本确认 IO/SQL 线程和已接收、已执行的 GTID
SHOW REPLICA STATUS\G

重点看 Retrieved_Gtid_Set 与 Executed_Gtid_Set,以及 Replica_IO_Running、Replica_SQL_Running。延迟秒数为零并不等于没有缺口;多线程副本还可能在最近执行的 GTID 中暂时出现间隔。切换前应让正在提交的事务完成,再重新取一次集合。

MySQL GTID 切换前旧主库与候选副本的 gtid_executed、gtid_purged 和复制状态静态关系说明图
图1:GTID 切换前的集合与状态关系说明图,帮助区分已执行事务、已清理日志和复制线程;这是静态说明图,不是运行截图。

用 GTID 集合证明候选副本已经追平

假设旧主库在写入冻结后记录的集合是 source_set,候选副本当前集合是 candidate_set。先让候选副本等待旧主库集合,再判断旧集合是否完整包含在候选集合中:

-- 将 source_set 替换为旧主库切换点的真实 GTID 集合
SELECT WAIT_FOR_EXECUTED_GTID_SET('source_uuid:1-820', 30) AS wait_result;

-- 结果为 1 表示旧主库集合已包含在候选副本的执行集合中
SELECT GTID_SUBSET('source_uuid:1-820', @@GLOBAL.gtid_executed) AS source_is_subset;

-- 排查候选副本是否有不应存在的独立写入;现场需结合拓扑确认
SELECT @@GLOBAL.gtid_executed AS candidate_set,
       @@GLOBAL.gtid_purged AS candidate_purged;

WAIT_FOR_EXECUTED_GTID_SET() 的超时返回只能说明等待窗口结束,不能替代后面的集合判断。对于一主一从、候选副本没有独立写入的拓扑,可以再用两个方向的 GTID_SUBSET() 判断集合是否相等;如果候选集合多出来源不明的 GTID,就先停下切换,查清它来自哪个来源或人工事务。

提升候选副本,旧主库保持只读

一致性证据成立后,先停止候选副本的复制,再切断旧来源。提升期间要让新主库先解除写保护,旧主库则继续保持只读,避免两个实例同时接受业务写入:

-- 候选副本已经追平后停止复制,并清理旧来源配置
STOP REPLICA;
RESET REPLICA ALL;

-- 解除新主库的写保护;生产环境应先完成连接路由切换
SET GLOBAL super_read_only = OFF;
SET GLOBAL read_only = OFF;

-- 旧主库作为副本重新接入时保持保护,并使用 GTID 自动定位
SET GLOBAL super_read_only = ON;
SET GLOBAL read_only = ON;
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = 'new-primary.example',
  SOURCE_PORT = 3306,
  SOURCE_AUTO_POSITION = 1;
START REPLICA;

RESET REPLICA ALL 会清理副本连接元数据,只有在确认候选副本不再需要旧来源后才执行。重新挂载旧主库时,账号、网络和 TLS 参数应按实际环境补齐;这里不把密码写进命令或文章。启动后重新检查 SHOW REPLICA STATUS\G,确认新的来源、自动定位和 SQL 线程状态。

MySQL GTID 新主库提升、旧主库只读回挂与故障回退边界的静态关系说明图
图2:提升与回退边界说明图,展示新主库、旧主库、写保护、自动定位和新增 GTID 的关系。

新主库有新写入后,不要盲目切回

切换成功后,真正决定能否回退的是新主库产生的 GTID 是否已经到达旧主库。若旧主库仍是只读副本,可以让它追赶新主库,然后比较新主库集合是否为旧主库集合的子集。只要这个前提无法证明,就不能把旧主库重新当主库。

回退清单可以压缩成四项:冻结新主库写入;记录新主库的 gtid_executed;等待旧主库执行这些 GTID;再次检查复制线程、集合关系和业务路由。若旧主库曾经独立写入,或二进制日志已经清理导致无法补齐,就应重新做备份恢复或全量同步,把它当成一次新的副本初始化,而不是“撤销上一次切换”。

检查项通过条件不通过时的动作
写入闸门旧主库和候选副本没有并行业务写入先停路由或启用维护闸门
GTID 集合来源集合是候选执行集合的子集等待复制并重新取集合
回退条件旧主库包含新主库产生的全部 GTID继续追赶或重新同步,禁止直接切回

常见问题

只看 Seconds_Behind_Source 为零可以切换吗?

不建议。它是延迟参考值,不能证明 GTID 集合完整,也不能发现候选副本的独立写入。切换仍应以集合比较和线程状态为准。

gtid_purged 变多是不是代表数据丢了?

不一定。它表示已经提交但不在当前二进制日志中的 GTID,是 gtid_executed 的子集。要判断是否可回退,应关注目标实例是否包含需要的 GTID,而不是只看数值大小。

自动定位能不能自动解决回退?

不能。SOURCE_AUTO_POSITION=1 能按 GTID 请求缺失事务,但它不会替你解决并行写入、日志已清理或数据分叉;回退前仍需冻结写入并完成集合核对。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>