xa recover 有结果但 xa rollback/commit 报 xaer_nota,说明事务元数据已丢失,innodb 内存和 undo 中无对应事务,仅日志残留;需人工核对 xid 并确认业务状态后谨慎清理。

XA RECOVER 查到结果但 XA ROLLBACK/COMMIT 报 XAER_NOTA,说明事务元数据已丢失,不能靠重试解决,必须人工核对 XID 并确认业务状态后谨慎清理。
为什么 XA RECOVER 有结果却执行不了 XA ROLLBACK
这不是命令写错了,而是 MySQL 内部已“忘记”这个 XID。常见原因包括:客户端崩溃、网络中断、应用没调 XA END 就断连,或 MySQL 异常重启后 binlog/XA log 恢复不完整。此时 XA RECOVER 读的是日志残留,而 InnoDB 内存和 undo 状态里已无对应事务。
关键点:
-
XA RECOVER输出的data字段是二进制,必须结合formatID、gtrid_length、bqual_length拼出完整 XID;单独复制data容易缺前导零或编码错误 - MySQL 8.0.29+ 默认开启
binlog_transaction_dependency_tracking = WRITESET,但 XA 仍依赖innodb_support_xa = ON(5.7 已废弃,8.0 兼容层仍检查);关掉它会导致XA RECOVER返回空——不是没悬挂,是压根不记录 - 若事务涉及外部资源(如 JTA 中的 MQ 连接),仅在 MySQL 端清理会引发数据不一致;必须先确认外部系统是否已提交/回滚
怎么确认这个 XA 事务是否真的还在“活锁”状态
别只信 XA RECOVER。它只读取日志,可能滞后或残留。更准的方式是查内存实时状态:
- 执行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state = 'PREPARED';如果这里为空,但XA RECOVER有结果,基本可判定是元数据不一致的“幽灵事务” - 查
SHOW ENGINE INNODB STATUS的TRANSACTIONS和LOGsection,看是否有 PREPARE 崩溃线索 - 检查
performance_schema.data_locks,确认是否有行锁/表锁被该 XID 持有;没有锁占用,大概率只是日志残留
安全清理残留 XA 记录的实操步骤
MySQL 没有 XA FORGET,所谓“清理”,本质是让 InnoDB 忘掉这个 XID,前提是确认它确实不会再被提交或回滚。操作前必须满足:
- 该 XID 对应的业务逻辑已终止,无重试机制,无跨库关联
- 已确认外部系统(如 MQ、其他 DB)未处于等待该 XID 提交的状态
- 已在从库比对过 binlog 位置,确认主库该 XID 未同步成功(避免主从冲突)
清理路径(按风险升序):
- 先用
XA RECOVER得到完整 XID,再尝试XA ROLLBACK 'xid_string';成功则结束 - 失败且确认无业务影响,可设
innodb_force_recovery = 1启动(5.7 可行,8.0 多数情况不生效),再连上执行XA ROLLBACK - 极端情况(如 ib_logfile 损坏),需停库删
ib_logfile*,但必须配合全量 dump + reload,否则 redo 不一致
使用 Cetus 的 xa-suspension-tool 自动化处理
Cetus 提供的 xa-suspension-tool(位于 xa-suspension-tool/xa-suspension.py)能自动比对 binlog 和 DB 状态,识别主从不一致的悬挂场景。但它不是万能的:
- 工具默认只处理“主库有 prepare、从库已 commit”的单边悬挂;对“从库有 prepare、主库已 rollback”的反向悬挂需手动指定
--rollback-slave参数 - 它依赖
mysqlbinlog解析,而 XA 的COMMIT/ROLLBACK默认不写 binlog(除非binlog_order_commits = OFF且sync_binlog = 1),所以解析不到不代表没发生 - 运行前务必备份
mysqlbinlog --base64-output=DECODE-ROWS -v输出,用于人工复核
真正难的从来不是执行哪条命令,而是判断“这个 XID 还有没有业务语义”。一旦误删,轻则双写漏写,重则跨系统数据不可逆错乱——这一步没法自动化,得人盯。











