纯 sql 层面无法真正实现跨库/跨服务的分布式事务一致性,因xa等机制仅适用于同质数据库集群,在高并发、网络分区等场景下易卡在prepared状态且人工干预成本高。

分布式事务在 SQL 场景下基本不可靠
直接说结论:纯 SQL 层面无法真正实现跨库/跨服务的分布式事务一致性。mysql 的 XA START、postgresql 的 PREPARE TRANSACTION 等机制只适用于同质数据库集群,且在真实高并发、网络分区、节点宕机场景下极易卡在 PREPARED 状态,人工干预成本极高。
常见错误现象:XA RECOVER 返回大量 PREPARED 事务;主库 crash 后从库无法自动回滚未提交分支;应用层调用 COMMIT 后部分分片成功、部分超时,最终状态不一致。
- 使用场景:微服务拆分后订单库、库存库、积分库各自独立 —— 这类架构下硬上 SQL 分布式事务等于埋雷
- 参数差异:
innodb_support_xa=ON在 MySQL 8.0.29+ 已默认关闭,开启后还会拖慢单机事务性能 - 兼容性影响:云数据库(如阿里云 RDS、AWS Aurora)普遍禁用或阉割 XA 支持,控制台甚至不暴露
XA相关命令
替代方案选型要看数据一致性要求等级
不是所有业务都需要强一致。先判断你的场景属于哪一类:
- 订单创建:允许“先占库存再异步扣减”,失败时发告警+人工补偿 —— 用
本地消息表 + 定时任务最稳 - 资金转账:必须严格守恒,但可接受秒级延迟 —— 用
TCC(try-Confirm-Cancel),每个服务自己管好try_balance和confirm_balance字段 - 日志归档同步:允许分钟级延迟、少量重复 ——
binlog + kafka + 消费端幂等写入更轻量
注意:SAGA 模式虽然概念流行,但在 SQL 场景里落地极难——因为绝大多数业务表没预留 compensate_sql 字段,回滚逻辑要手写且难以覆盖所有异常路径。
如果非要用 XA,请死守三个操作边界
真有历史包袱绕不开 XA,就只能靠约束使用方式来压风险:
- 事务生命周期必须 ≤ 3 秒,超时自动
XA ROLLBACK(需自建监控脚本轮询XA RECOVER输出) - 禁止在 XA 分支里调用外部 http、访问缓存、写文件 —— 所有依赖必须是同一数据库实例内的表
- 应用层必须实现
XA END / XA PREPARE / XA COMMIT的完整重试逻辑,尤其XA PREPARE失败时不能静默丢弃,要记日志并告警
示例陷阱:START TRANSACTION; INSERT INTO t1 ...; XA START 'tx1'; INSERT INTO t2 ...; XA END 'tx1'; XA PREPARE 'tx1'; —— 这里 START TRANSACTION 和 XA START 混用会导致隐式提交,t1 数据提前落盘,XA 已经失效。
最常被忽略的其实是事务上下文传递
很多团队以为把 SQL 写对就完了,结果在 spring Cloud 或 dubbo 里跑着跑着就出现“部分服务没进事务”。根本原因是:ThreadLocal 无法跨线程/跨进程传递。
典型表现:@Transactional 注解在 A 服务生效,但调用 B 服务的 rpc 接口后,B 服务里的 INSERT 不受任何事务控制,变成自动提交。
- 解决方案只有两个:用支持分布式事务上下文的框架(如 Seata 的
AT模式),或者手动把xid作为请求头透传,B 服务收到后主动绑定到本地事务 - 云厂商 SDK(如阿里云 MSE、腾讯云 TSF)默认不透传事务上下文,得自己改
FeignClient拦截器或Dubbo Filter - 一旦用了消息队列做解耦,
MQ 发送动作本身就必须和 DB 操作放在同一个本地事务里,否则必然出现“DB 成功但 MQ 没发出去”
复杂点在于:这些上下文传递逻辑没法靠 SQL 语句解决,得在应用代码里一处处对齐,漏一处就破功。