MySQL XA 的两个错序 Bug:GTID 已可见,XA RECOVER 里却还看不到(或还没消失)
信源:MySQL 9.7.4 Release Notes(2026-10-20)Replication Notes 中的两条 XA 修复。
一个 XID 的生命周期
XA 两阶段提交里,一个 XID 的生命周期是:
XA START -> 业务 SQL -> XA END -> XA PREPARE(进入 PREPARED,落盘到 mysql.xa_... 相关存储)
-> 外部事务协调器决定 XA COMMIT 或 XA ROLLBACK
外部协调器(自研 TCC、Seata、ShardingSphere、消息队列事务消息等)通常依赖两件事做决议:
- 用 GTID 判断“这个分支到底提交了没有”,读
gtid_executed,或者在下游复制链路里等 GTID; - 用
XA RECOVER列出仍在 PREPARED 状态、需要继续决议的 XID。
问题在于,修复前这两套状态的暴露顺序没有保证,GTID 可见不代表事务可观察。
修复前的两个错序
场景 A(commit 侧):GTID 先出现,XA RECOVER 还在列它
“The GTID for an XA COMMIT or XA ROLLBACK could become visible before XA RECOVER stopped listing the completed XID. XA RECOVER now hides the finalized transaction before the GTID is externalized.” (Bug #36505659)
XA COMMIT 已经执行,GTID 立刻出现在 gtid_executed 里;但 XA RECOVER 的输出中那条 XID 还没消失,因为列表清理晚一步。对账程序看到“GTID 已存在”判定为已提交,同时又看到 XA RECOVER 里还有它,于是重复发起决议,或者把这笔业务误报成悬挂事务。
场景 B(prepare 侧):GTID 先出现,XA RECOVER 还看不到
“After XA PREPARE made its GTID visible in gtid_executed, XA RECOVER could still omit the prepared XID. The XID is now made visible before the GTID state is updated, so waiting for the GTID guarantees that another session can observe the prepared transaction.” (Bug #35872823)
XA PREPARE 的 GTID 已经在 gtid_executed 可见,但 XA RECOVER 还看不到这条 XID。官方给出的语义是“等 GTID = 可以观察到 prepared 事务”,协调器按这个语义等到 GTID 后就去 XA RECOVER 找 XID 继续决议,结果找不到,可能把它当成“事务不存在”直接放弃,分支就此悬挂。
为什么会导致漏单和重复
两个错序指向同一个根因:GTID 状态和 XA RECOVER 可见性是两套独立推进的状态,中间存在窗口。
- 场景 B 的窗口里,协调器“确认过 GTID 却捞不到 XID”,最容易走向“事务不存在”的结论并跳过决议,分支永远停在 PREPARED,对账侧表现为少一笔(漏单)或单边记账。
- 场景 A 的窗口里,同一笔 XID 同时呈现“已提交”和“待决议”两种表象,幂等没做好的协调器会再发一次
XA COMMIT/XA ROLLBACK,走到重复扣款、重复入账。
修复后的语义
两条修复把顺序对调了:
- 先让 XID 可见,再更新 GTID 状态 → 等待 GTID 就能保证另一个会话观察到 prepared 事务;
- 先隐藏 finalized 事务,再暴露 GTID → GTID 出现时,
XA RECOVER一定不再列出它。
也就是说,修复之后“等 GTID”这个动作才真正等价于“事务状态可观察”。
实操:别拿 GTID 和 XA RECOVER 做单点判断
修复进了 9.7.4,旧版本仍要自己兜住窗口期:
- 不要用“GTID 可见”和“XA RECOVER 输出”做单点判断,两者是两套状态,存在窗口。
- 决议前用
XA RECOVER拿到 XID,再以XA COMMIT/XA ROLLBACK的返回码为准;重复决议必须幂等,对已完成的 XID 返回XAER_NOTA视为已决议。 - 对账脚本加一个宽限窗口:同一 XID 在 T 秒内既出现在
gtid_executed又在XA RECOVER里,判定为收敛中,不报警。
检查悬挂 XA 的三条命令:
SELECT * FROM information_schema.INNODB_TRX; -- 看不到 prepared XA
XA RECOVER; -- 权威列表
SELECT @@global.gtid_executed;
复制链路上注意:从库未开 log_replica_updates / binlog 时,XA 的可见性与主库不同,不要用从库的 gtid_executed 判断分支状态。
涉及 Bug:#36505659、#35872823。修复进入 9.7.4,旧小版本自行核对。