主从切换后SELECT @@global.gtid_executed不一致说明从库未完全追上原主库GTID集合或存在“孤儿事务”,是数据不一致的必要预警信号,需用GTID_SUBTRACT比对差集定位缺失事务范围。

主从切换后 SELECT @@global.gtid_executed 不一致说明什么
说明从库没完全追上原主库的 GTID 集合,或者切换过程中有事务在旧主上提交但未同步到新主(即“孤儿事务”)。这不是数据不一致的充分证据,但它是必要预警信号。
- 先查两边:
SELECT @@global.gtid_executed;—— 如果新主的 GTID 集合不包含旧主最后几个UUID:NNN,那些事务大概率没落库 - 特别注意:如果旧主已关机或不可达,
gtid_executed是它停机前的最终快照;新主的值是当前实际执行过的集合,二者必须“包含关系”,否则就有漏 - MySQL 5.7+ 默认开启
enforce_gtid_consistency=ON,但不保证网络中断时事务不丢失——GTID 只标识“谁干了啥”,不保证“一定干成了”
怎么定位具体哪几个事务成了孤儿
靠比对 gtid_executed 差集最直接。别翻 binlog 文件名和偏移量,那在 GTID 模式下已过时。
- 用 MySQL 自带函数算差集:
SELECT GTID_SUBTRACT('aaa-bbb-ccc-1-100', 'aaa-bbb-ccc-1-95');返回aaa-bbb-ccc-1-96-100 - 把旧主的
gtid_executed当做 A,新主的当做 B,执行GTID_SUBTRACT(A, B)—— 结果非空就代表孤儿事务范围 - 如果结果里有多个 UUID 段(如
uuid1:1-5, uuid2:10-15),说明旧主上不止一个 server_uuid 曾写入过,得分别查对应实例的 binlog - 注意:如果旧主启用了
log_slave_updates=OFF,它的 binlog 根本没记录从库回放的事务,此时 GTID 差集会误报——先确认该配置是否开着
手动回滚孤儿事务前必须验证的三件事
别急着 mysqlbinlog | mysql 回放或手写 ROLLBACK。事务已提交,只能靠反向 SQL 补偿,而补偿本身可能出错。
- 确认这些事务是否真影响了业务状态:查应用日志、监控指标、关键表的最新变更时间,避免“为回滚而回滚”
- 检查涉及表是否有行锁等待、长事务未提交,否则补偿 SQL 会被阻塞,甚至引发死锁
- 在从库上先用
SET SESSION sql_log_bin = 0;执行补偿语句,验证逻辑和权限;切记不能在新主上直接关 binlog 写数据,否则 GTID 会乱 - 如果事务含 DDL(比如
ALTER TABLE),补偿几乎不可行——DDL 不支持回滚,只能人工核对结构差异并修复
mysqlbinlog --base64-output=DECODE-ROWS -v 解析出来的事务怎么对应到 GTID
解析结果顶部的 # at XXXX 和 #190101 12:34:56 server id Y end_log_pos Z CRC32 XXXX 都不管用;唯一可靠的是开头的 SET @@SESSION.GTID_NEXT= 'uuid:nnn'; 行。
- 每个事务开头必有且仅有一行
SET @@SESSION.GTID_NEXT,后面跟着的就是这个事务的完整 GTID - 如果看到
GTID_NEXT='AUTOMATIC',说明这事务来自非 GTID 模式实例(或gtid_mode=OFF),它根本不会出现在gtid_executed里,也不参与差集计算 - 解析时务必加
--skip-gtids参数再重放,否则会因 GTID 冲突报错The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1 - 不要依赖
mysqlbinlog --read-from-remote-server直连旧主取日志——如果旧主已下线或 binlog 被 purge,这条路就断了
GTID 差集能告诉你“少了什么”,但没法自动告诉你“该怎么补”。真正麻烦的永远不是识别,而是补偿逻辑是否覆盖了所有分支路径、是否破坏了幂等性、以及有没有人正在读那个刚被你 update 的字段。










