MySQL XA在分库场景基本不可用,因协调者崩溃后无法全局判断分支状态,且各节点版本、binlog、时钟等不一致导致XA COMMIT失败;Seata AT需手动建全字段undo_log表;最终一致性方案更可控。
MySQL XA 事务为什么在分库场景下基本不可用
XA 协议理论上支持跨库两阶段提交,但实际在分库(比如 ShardingSphere 或自研分片)中几乎无法稳定落地。根本原因不是协议本身错,而是协调者(TM)和参与者(RM)的故障恢复能力严重不对等。
MySQL 的
只能查出本实例上未完成的 XA 分支,一旦 coordinator 崩溃,没有全局视图,无法判断哪些分支该 commit、哪些该 rollback
分库环境下,不同物理库的 MySQL 版本、binlog 格式、server_id 甚至时钟可能不一致,导致
成功后,某节点在崩溃恢复时拒绝
ShardingSphere 等中间件默认关闭 XA 支持,开启后需额外部署
或
,但它们对 MySQL 的 XA 日志解析有版本限制(例如不兼容 MySQL 8.0.33+ 的 redo log 优化)
Seata AT 模式下 undo_log 表必须手动建且字段不能少
Seata 的 AT 模式靠本地
表记录回滚日志,但它不会自动建表,也不校验表结构。很多团队上线后才发现事务卡住或回滚失败,根源是字段缺失或类型不匹配。
必须包含
(BIGINT NOT NULL)、
(VARCHAR(128) NOT NULL)、
(VARCHAR(128) NOT NULL)、
(LONGBLOB NOT NULL)、
(TINYINT NOT NULL)、
(DATETIME NOT NULL)、
(DATETIME NOT NULL)
类型必须是
,用
会导致大事务序列化失败,错误信息类似
每个分库都要建同名同结构的
表,Seata 不支持跨库路由该表
本地消息表 + 最终一致性比强一致更可控
真正线上扛量的分布式事务,90% 以上走的是“可靠消息 + 对账补偿”,而不是试图用框架模拟 ACID。它不依赖数据库层的复杂协调,出问题时可查、可重试、可人工介入。
消息表必须和业务表在同一库同一事务中写入,用
+
包在同一个
里
投递服务轮询时要用
,避免多个 worker 重复取同一条消息;更新状态时要带条件,如
不要把重试逻辑全交给 MQ(比如 RabbitMQ 的死信队列),它的重试间隔不可控,且无法关联原始业务上下文;应由独立的调度服务管理重试次数、退避策略和告警阈值
TCC 接口的 Confirm/Cancel 必须幂等且不依赖 Try 阶段状态
TCC 是最灵活也最容易翻车的模式。很多人以为只要三个接口写出来就完事了,结果压测时大量
失败,日志里反复出现
。
和
必须设计成纯幂等操作:比如扣库存用
,而不是先
再判断
不能假设
一定成功或已落库——网络超时、应用重启都可能导致
执行了但没返回响应,此时 Seata 会直接发
,你的接口必须能凭
和业务参数自行判断是否该执行
不是
的逆操作,而是“确保最终不发生”。例如
预占额度,
就是释放预占,哪怕预占记录已丢失,也要确保用户额度不被多扣
分布式事务真正的难点从来不在怎么写代码,而在于怎么定义“事务边界”——哪些操作必须原子,哪些可以妥协,以及当系统部分失联时,你愿意接受多久的数据不一致。这些决策没法靠框架自动做。
XA RECOVERXA PREPAREXA COMMITSeata ATAtomikosundo_logbranch_idxidcontextrollback_infolog_statuslog_createdlog_modifiedrollback_infoLONGBLOBTEXTjava.io.EOFException: Unexpected end of ZLIB input streamundo_logINSERT INTO msg_table (...) VALUES (...)UPDATE biz_table SET status = 'sent' WHERE ...BEGIN/COMMITSELECT ... FOR UPDATE SKIP LOCKEDUPDATE msg_table SET status = 'sending' WHERE id = ? AND status = 'ready'Confirmno try record found for xidConfirmCancelUPDATE stock SET qty = qty - ? WHERE sku_id = ? AND qty >= ?SELECTTryTryConfirmxidCancelTryTryCancel