xa协议仅提供2pc框架,跨库一致性需应用层或外部事务管理器兜底,否则易致部分提交、悬挂事务及主从错乱;xa start/end/prepare须严格配对,漏xa end将使事务卡在active状态,无法被xa recover发现。

MySQL 的 XA 协议本身不解决跨库一致性问题,它只提供一个两阶段提交(2PC)的协议框架;真正的一致性必须由应用层或外部事务管理器兜底,否则极易出现部分提交、悬挂事务、主从状态错乱等生产事故。
XA START / END / PREPARE 必须严格配对执行
漏掉 XA END 是最常见也最隐蔽的错误:事务会卡在 ACTIVE 状态,既不能 XA PREPARE,也无法被 XA RECOVER 查到,最终耗尽连接池或引发锁等待超时。
-
XA START 'tx1'后必须紧跟着业务 SQL,完成后立刻执行XA END 'tx1';中间不能有异常跳过,也不能依赖连接关闭自动清理 -
XA END之后不能再执行任何 DML,否则报ERROR 1397 (XAE05): XAER_RMFAIL - 建议封装成带 panic 捕获的函数,比如 Go 中用
defer确保XA END执行,Java 中用 try-with-resources +XAResource.end()
XA PREPARE 成功 ≠ 事务安全,失败也不等于回滚完成
XA PREPARE 是临界点——成功表示资源已锁定、undo/redo 日志已刷盘,但此时事务仍处于“prepared”状态,数据未真正落库;失败则可能因磁盘满、binlog 写入失败、MyISAM 表混用等导致静默丢弃,而 XA RECOVER 查不到记录。
- 崩溃重启后,
XA RECOVER只显示那些成功写入 redo 且被 InnoDB 记录的 prepared 事务;若 crash 发生在 prepare 之后、commit 之前,且 binlog 未刷盘,该事务就“消失”了,只能靠备份恢复 -
XA RECOVER输出的DATA字段是 base64 编码的 XID,需解码确认对应业务单号,避免误XA ROLLBACK已在其他节点提交的事务 - 上线前必须配置定时任务(如每 5 分钟)执行
XA RECOVER,检查FORMATID和GTRID_LENGTH非零但无后续操作的记录
MySQL 不是协调者,跨实例 XA 实际是应用层硬编排
MySQL 官方不支持跨实例自动 XA 协调。所谓“多 MySQL 实例 XA”,本质是应用代码分别连不同实例,各自执行 XA START/XA PREPARE,再由自己控制全局提交顺序——这意味着你得手动保证所有 prepare 成功才发 commit,任意一环失败就得全量 rollback,且无法自动重试。
- ProxySQL、MaxScale 等代理会破坏 XA 会话上下文,导致
XA RECOVER查不到分支事务 - MySQL 8.0.29+ 默认启用
binlog_transaction_dependency_tracking = WRITESET,它会重排 XA 分支的 binlog 写入顺序,造成从库上XA COMMIT失败或状态不一致,必须显式设为COMMIT_ORDER - 如果用 ShardingSphere 等中间件,
transactionType=XA要求每个分片库都有undo_log表;缺一个,整个事务 fallback 到本地事务,一致性彻底丢失
比 XA 更现实的选择:放弃强一致,换最终一致
真正落地时,90% 以上的跨库场景不该用 XA——性能差、运维重、故障恢复难。更可行的是把“事务”拆成可观察、可补偿、有明确状态的本地操作。
- 订单扣款成功后,发 MQ 消息触发库存扣减;失败则走独立补偿任务查 MQ 状态 + 重试
- 用
SELECT ... FOR UPDATE锁行只限同库同表;跨库锁无效,还容易死锁 - 避免在事务中调用 HTTP 或 RPC:网络超时会导致 UNDO LOG 无法回滚,留下悬挂事务
- 如果必须保留 XA 接口(如对接老系统),至少禁用
innodb_support_xa以外的所有非事务引擎,且确保所有参与表都是 InnoDB
最容易被忽略的不是语法怎么写,而是 XA 事务在崩溃后根本不会自动决定该 commit 还是 rollback——它不知道业务语义,只等你人工解码 XID 并拍板。这决定了它从来就不是一个“开箱即用”的一致性方案,而是一套需要深度理解、主动运维、随时准备救火的机制。











