尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SEATA XA模式实战:分布式事务原理、配置与避坑指南

SEATA XA模式实战:分布式事务原理、配置与避坑指南 看到这个标题我先是一愣——“SEATA分布式事务——XA模式巫”后来一拍大腿明白了这大概说的是 SEATA 的 XA 模式那个‘巫’字我猜要么是笔误要么是想表达 XA 模式里那些“玄学”级的问题。说实话干这行这么多年分布式事务从我最早手写消息对账到后来接入各种中间件XA 模式确实是我见过最容易“看着简单、跑起来见鬼”的方案。原理就两阶段提交一句话能讲完可一旦落到数据库连接、全局锁、分支事务注册这些细节里坑是一个接一个。这篇文章我就把自己实际落地 SEATA XA 模式的完整思路拆给你看不绕弯子直接讲清楚它到底怎么工作、跟 AT 模式有什么本质区别、实际项目里怎么配置怎么写代码、以及我踩过的那些报错和排查过程。无论你是刚接触分布式事务的新手还是已经在生产环境里被数据不一致折磨过的老手这篇都能给你一个可以照着用的参考答案。1. 入手前先搞清楚分布式事务到底在解决什么问题1.1 微服务下单场景里的经典难题讲 XA 之前得先把问题的源头摆出来。假设你有一个商城系统拆成了订单服务和库存服务订单库在 A 数据库库存库在 B 数据库。用户下单这个动作在代码里是两步订单服务往订单表插入一条订单记录订单服务调用库存服务让库存服务扣减对应商品的库存。业务要求很明确这两步要么都成功要么都失败。如果订单写进去了库存扣减却失败了用户会看到订单存在但发货时没货如果库存扣了但订单没写成功货没了钱也没了更麻烦。在单库时代这个问题用数据库的本地事务就解决了。你把两步操作放在同一个数据库事务里BEGIN之后执行 SQL要么COMMIT要么ROLLBACK数据库帮你保证原子性。但服务拆了、库也拆了之后本地事务只能管住自己那一个库的连接它根本不知道另一个服务里的 SQL 执行得怎么样。你没法让订单库的事务和库存库的事务合并成一个事务这就是分布式事务要解决的“跨多个数据源的一致性”问题。很多刚接触微服务的同学会觉得我用消息队列把库存扣减改成异步或者干脆在订单服务里 catch 异常再调库存服务的回滚接口是不是就行了这些确实是补偿方案的雏形但都绕不开一个问题你缺少一个统一的、可信的“协调者”来告诉所有参与方这一笔全局操作到底该提交还是该回滚。SEATA 这个中间件扮演的正是这个协调者的角色。1.2 从本地事务到全局事务的演进思路本地事务有个很形象的说法叫“单点提交”所有操作都发生在同一条数据库连接上。分布式事务则是“多点协调”模型变成一个全局事务之下挂着多个分支事务每个分支事务负责操作一个数据源全局事务结束时统一决定所有分支的最终去向。在 SEATA 的语境里这个模型有三个核心角色事务协调器TCTransaction Coordinator独立部署的 SEATA Server负责管理全局事务的注册、状态记录、提交或回滚的决策事务管理器TMTransaction Manager业务代码里发起全局事务的一方通常就是被GlobalTransactional标记的方法它负责向 TC 申请开启全局事务并在方法结束时触发全局提交或回滚资源管理器RMResource Manager每个参与全局事务的数据源在 SEATA 里就是被代理过的数据源它负责管理分支事务向 TC 注册分支、上报执行状态接收 TC 的二阶段指令。这个模型理解透了后面看 XA 模式和 AT 模式的区别就轻松了。两种模式在 TC、TM、RM 的角色划分上完全一样真正的差别在于“分支事务在全局事务运行期间做了什么”。2. SEATA XA 模式的原理拆解2.1 回首 XA 规范本身XA 规范不是 SEATA 发明的它是由 X/Open 组织提出的分布式事务处理标准主要定义了全局事务管理器TM和局部资源管理器RM之间的接口。你可以把它理解成一套“数据库原生支持分布式事务”的协议后端是各个数据库厂商自己实现的比如 MySQL 的 InnoDB 存储引擎、Oracle 的 XA 支持、PostgreSQL 的 PREPARE TRANSACTION都是按这套规范来的。XA 最核心的机制就是两阶段提交2PC。第一阶段叫准备阶段Prepare事务管理器询问所有参与资源“能不能提交”每个资源执行完 SQL 之后不提交而是写日志、持有锁然后回复“我准备好了”。第二阶段叫提交阶段Commit/Rollback事务管理器根据所有资源的准备结果做决策如果所有人都说准备好了就通知所有人提交只要有一个人说不行就通知所有人回滚。这套机制本身做到了足够强的原子性和隔离性——因为所有资源在第一阶段都没真正提交全局读自己未提交的数据是不可能的全局未提交之前其他事务也动不了那些被锁住的行。但它也是有名的“性能杀手”因为第一阶段到第二阶段之间所有资源都得一直持有数据库锁和连接。这也是为什么很多人说 XA 模式“重”——重就重在它对资源占用毫不留情。2.2 SEATA 如何落地 XA 模式SEATA 的 XA 模式做的事情本质上就是替你管理好两阶段提交的流程并把这套流程嵌入到你对本地数据源的操作里让你业务代码只需要加一个注解就能获得全局事务能力。具体落地过程分两步一阶段业务方法里执行的每条 SQL 都发生在同一个 XA 分支事务内部。SEATA 通过代理数据源拦截了对Connection的获取和 SQL 执行它会自动向 TC 注册分支事务但不会让连接真的提交。也就是说SQL 执行完了数据也写进了数据库的 “事务内视图”但全局视角上这笔数据还处于“未决”状态连接也不释放。二阶段TM 发来全局提交或全局回滚指令TC 会通知该全局事务下所有分支对应的 RM。如果全局提交RM 对那个 XA 分支执行XA COMMIT如果全局回滚RM 执行XA ROLLBACK。到了这一步数据库才真正把数据落盘或者把数据撤销。这个流程里最值得注意的是业务代码本身不需要写任何XA START、XA END、XA PREPARE这类原生 XA 指令SEATA 的数据源代理把这些操作都封装掉了。你只需要保证业务里获取的Connection来自 SEATA 代理过的数据源全局事务就能被正确纳入管理。2.3 XA 模式下的事务隔离性分析隔离性是分布式事务里特别容易被人忽略的一块。XA 模式的隔离性可以分成两个维度来看。第一个维度是本地隔离。在 MySQL InnoDB 里默认隔离级别是 REPEATABLE READ。XA 分支事务执行期间由于没有提交其他事务是看不到它写入的数据的这是数据库本地隔离级别提供的保障。第二个维度是全局隔离。XA 模式下分支事务在全局事务提交前一直不提交所以对于其他全局事务来说它们看不到这个尚未提交的全局事务的中间状态。这一点跟 AT 模式完全不同AT 模式里一阶段就直接提交了本地事务通过全局锁来防止脏写但读操作可能读到未全局提交的“脏数据”。XA 模式因为“不提交”这个特性天然不会出现这种全局层面上的脏读问题这是 XA 模式在一致性上最大的底气。但代价也很直观锁的持有时间是整个全局事务的执行时间而不仅仅是本地 SQL 的执行时间。如果全局事务里有慢查询、有远程调用等待那锁和连接就会被一直占着。这也是为什么 XA 模式不太适合长事务、高并发场景。3. XA 模式实操订单与库存的全局事务3.1 案例背景与项目结构我拿一个最经典的下单扣库存场景来做演示。两个服务订单服务和库存服务各自连各自的库。正常情况下下单流程是订单服务开启全局事务订单服务插入订单记录订单服务通过 Feign 调用库存服务扣减库存库存服务扣减库存成功返回成功全局事务提交。异常情况是如果库存扣减失败或者库存不足订单服务抛出异常全局事务回滚订单记录也不能落库。技术栈我这边用 Spring Boot 2.7 Spring Cloud Alibaba Seata 1.8.0 MySQL 8.0。为什么选 Seata 1.6 以上的版本呢因为 1.6 之后 XA 模式在 MySQL 8.0 下的分支事务注册和全局锁处理更成熟早期版本会碰到一些驱动兼容问题后面我讲到坑的时候会细说。表结构很简单就两张表-- 订单库 CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint DEFAULT NULL, product_id bigint DEFAULT NULL, amount decimal(10,2) DEFAULT NULL, status tinyint DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB; -- 库存库 CREATE TABLE stock ( id bigint NOT NULL AUTO_INCREMENT, product_id bigint DEFAULT NULL, stock_count int DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB;注意XA 模式不需要 SEATA AT 模式里的undo_log表因为回滚是数据库通过XA ROLLBACK自己完成的不是靠反向 SQL 补偿。很多同学把 AT 模式的习惯带过来非要去建undo_log表其实 XA 模式下那表根本用不上。3.2 关键配置步骤SEATA XA 模式的配置核心就三个点客户端数据源代理、SEATA 注册中心连接、全局事务注解。先看application.yml里跟 SEATA 相关的部分spring: application: name: order-service cloud: alibaba: seata: tx-service-group: my_test_tx_group seata: registry: type: nacos nacos: server-addr: 127.0.0.1:8848 config: type: nacos nacos: server-addr: 127.0.0.1:8848 >Configuration public class DataSourceConfig { Bean ConfigurationProperties(prefix spring.datasource) public DataSource dataSource() { DruidDataSource dataSource new DruidDataSource(); return dataSource; } Bean public DataSource xaDataSource(DataSource dataSource) { return new DataSourceProxyXA(dataSource); } }DataSourceProxyXA是 SEATA 在 XA 模式下提供的数据源代理类。它内部会把普通的数据库连接包装成 XA 连接在事务执行过程中自动完成XAResource的注册、准备、提交或回滚。如果你是纯手写 Spring 配置而不是用 Spring Boot 自动装配记得把spring.datasource的自动配置排除掉不然你的DataSource会被业务代码直接拿去用代理就白做了。我建议在配置类里加一个检查逻辑启动时确认数据源确实是DataSourceProxyXAPostConstruct public void checkDataSource() { Assert.isInstanceOf(DataSourceProxyXA.class, dataSource, dataSource must be DataSourceProxyXA); }这个检查看着多余实际排查问题的时候能帮你省掉很多时间。我有一次就是配置写错Seata 全局事务一直不生效数据不一致了半小时最后发现数据源代理没配上。3.3 核心代码实现业务代码的核心就一个注解加正常的业务逻辑。订单服务里下单方法Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private StockFeignClient stockFeignClient; GlobalTransactional(rollbackFor Exception.class) public void createOrder(Order order) { // 1. 插入订单 orderMapper.insert(order); // 2. 远程扣减库存 stockFeignClient.deductStock(order.getProductId(), order.getAmount()); } }库存服务里扣减库存的方法Service public class StockService { Autowired private StockMapper stockMapper; Transactional(rollbackFor Exception.class) public void deductStock(Long productId, BigDecimal amount) { int updated stockMapper.deductStock(productId, amount); if (updated 0) { throw new RuntimeException(库存不足); } } }注意两个细节。第一订单服务的createOrder加的是GlobalTransactional库存服务的deductStock加的是Transactional。XA 模式下这两个注解的分工完全不同GlobalTransactional负责向 SEATA TC 注册全局事务并协调所有分支的最终提交或回滚Transactional负责让库存服务本地的方法纳入同一个分支事务里保证库存服务内部多条 SQL 也在同一个事务边界内。如果你在库存服务上也加了GlobalTransactional那就会重复开启全局事务一般会导致分支事务注册冲突报错让你摸不着头脑。第二rollbackFor Exception.class必须显式声明。Spring 的Transactional默认只对RuntimeException和Error回滚如果你自定义了一个继承Exception的业务异常不声明rollbackFor的话异常抛出去了事务却不会回滚。我在项目里见这种问题太多了排查到最后都发现是异常类型没配回滚策略。3.4 运行效果与分支事务验证启动 SEATA Server 和两个服务后用正常的参数调一次下单接口然后去 SEATA 控制台或者直接查 SEATA Server 的日志能看到全局事务的坐标和被注册的分支事务。全局提交成功时SEATA 服务端日志里会出现类似这样的信息global tx committed, xid: 192.168.1.100:8091:88491234567, branchId: 88491234568分支事务提交时数据库这边也能感知到。如果你想直观地看 XA 事务有没有生效可以在数据库里开启general_logSET GLOBAL general_log ON;然后观察 MySQL 日志会看到类似XA START、XA END、XA PREPARE、XA COMMIT这样的原生 XA 语句。看到这些语句就说明 SEATA 确实在用 XA 协议跟数据库交互而不是空挂了一个全局事务名头。为了验证回滚是否可靠我故意在库存服务里加了一个抛异常的逻辑让订单插入成功、库存扣减失败。调用接口后查看订单表发现订单记录没有落库。再把库存服务异常去掉正常调用一次订单表和库存表数据都正确写入了。这说明全局事务在跨服务场景下确实做到了要么都成功、要么都失败。3.5 实操现场XA 模式下连接与锁的行为在跑通基本功能之后我额外做了一次压测目的是观察 XA 模式下数据库连接和锁的持有行为。我用 100 个并发线程同时下单同一件商品每个下单请求内部故意休眠了 1 秒模拟慢处理然后看数据库的information_schema.innodb_trx表。结果很典型在全局事务未提交之前所有订单服务的数据库连接都处于RUNNING状态且对应的行锁和间隙锁全部被持有其他请求这些行都会被阻塞。这直接印证了我前面说的——XA 模式的锁是在全局事务一阶段开始时就持有直到二阶段结束才释放。并发越大全局事务持续时间越长阻塞就越严重。这个测试也反映出一个实际项目中很重要的问题如果下单流程里有外部调用、有复杂的业务计算、有网络等待把整个流程都塞进一个 XA 全局事务里数据库的连接和锁会被白白占用很久。所以用 XA 模式一定要控制单个全局事务的粒度尽量让事务里只包含必要的数据库操作远程调用能挪出去就挪出去。4. XA 模式与 AT 模式的对比以及选型建议4.1 两种模式的机制差异全解析SEATA 最知名的两种模式就是 AT 模式和 XA 模式。很多人问我它们到底有什么区别是不是 AT 模式就一定能替代 XA。这个问题回答起来其实不复杂关键是搞清楚两种模式在分支事务上的处理方式。AT 模式的思路是“补偿式回滚”。一阶段分支事务执行 SQL 后直接本地提交同时记录修改前后数据的快照也就是undo_log表。二阶段如果全局要回滚SEATA 根据undo_log里的快照生成反向 SQL把数据恢复到修改前。XA 模式的思路是“锁住不提交”。一阶段分支事务执行 SQL 后不提交数据一直以“未决事务”的形式存在。二阶段如果全局提交数据库执行提交如果全局回滚数据库直接回滚。这两者最核心的差异有几个维度AT 模式XA 模式一阶段是否提交本地直接提交不提交保持未决状态二阶段回滚方式基于undo_log反向 SQL 补偿数据库原生XA ROLLBACK对业务侵入性需建undo_log表需代理数据源只需代理数据源无需额外表全局隔离性读操作可能读到未全局提交的数据需要全局锁防脏写未提交前其他事务读不到隔离性好数据库支持理论上支持所有有 SQL 的库但 undo 回滚依赖 SQL 兼容性依赖数据库原生 XA 支持性能损耗一阶段提交快锁持有时间段但二阶段回滚要解析快照生成反向 SQL锁和连接持有到全局事务结束长时间事务开销大跨资源支持对非数据库类资源如消息队列支持较弱理论上只要是支持 XA 的资源都可以但 SEATA 主要支持关系型数据库4.2 什么时候选 XA什么时候选 AT选型这事我一直不建议无脑跟风。我自己的判断标准是先看业务对一致性的敏感度和事务的执行时间。如果你的业务属于短事务、高并发、单次事务内没有远程调用、没有长时间的数据库操作比如简单的账户余额扣减、核心交易链路优先考虑 AT 模式。AT 模式一阶段就提交锁持有时间短并发吞吐能力明显更好undo_log反向 SQL 虽然有点损耗但比 XA 长时间锁资源好得多。如果业务对全局读一致性要求很高不希望其他事务在全局事务提交之前读到中间状态或者你的数据库操作本身就在一个库内、只是涉及多个服务的数据源这时候选 XA 模式更合适。XA 模式天然避免脏读不需要像 AT 那样靠额外的全局锁来保证写隔离实现上更简单心智负担也小。还有一个场景也适合 XA你的团队对 SEATA AT 模式的undo_log机制不够熟悉担心反向 SQL 在某些复杂 SQL 下执行结果不正确那 XA 模式更稳妥因为回滚是数据库自己做的不涉及你代码里的 SQL 逻辑。我之前在一个账务系统里就遇到过一个问题一条 UPDATE 语句的WHERE条件里带了一个函数计算AT 模式的undo_log生成的反向 SQL 在某些边界数值下把数据改错了。排查了半天最后定位到是反向 SQL 对函数计算的处理存在偏差。这种场景下切到 XA 模式就再没出过问题。4.3 技术选型之外的一条建议选型的时候除了看模式本身的机制差异还得看团队的运维能力和中间件稳定性。XA 模式对数据库驱动版本、连接池配置、SEATA 版本的兼容性要求非常高稍微有点偏差就会出现各种诡异问题。AT 模式对 SEATA 自身的依赖更多但出问题时排查手段更多社区资料也更多。所以我的建议是没把握时就先用 AT 模式跑通业务把注册中心、配置中心、数据源代理这些基础链路弄稳定再根据业务特性评估要不要切换 XA。切换的成本并没有想象中那么高因为业务代码不需要改动只需要调整数据源代理类型和去掉undo_log建表脚本但这需要你有足够的时间做回归测试。5. XA 模式实战中的坑与排查经验5.1 数据库驱动和版本的兼容性问题XA 模式第一个让我印象深刻的坑出现在 MySQL 驱动上。MySQL 5.x 时代用的驱动是com.mysql.jdbc.Driver到了 MySQL 8.0 变成了com.mysql.cj.jdbc.Driver。SEATA 的 XA 数据源代理在识别数据库类型和生成原生 XA 指令时对不同驱动的解析逻辑不一样。如果你用 MySQL 8.0 但还是在 pom 里引入的是 5.x 的驱动XA 分支在做 prepare 阶段就会报各种各样的驱动方法不存在或者参数类型不对的错误。解决方式很简单统一用官方推荐的驱动版本。MySQL 8.0 就用dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency另外PostgreSQL 的 XA 支持跟 MySQL 不太一样它用的是PREPARE TRANSACTION机制需要把max_prepared_transactions参数调大否则并发稍微高一点就报sorry, too many prepared transactions。Oracle 的 XA 则需要额外的XAResource配置。如果你用的不是 MySQL一定要先去看数据库本身对 XA 的限制别指望 SEATA 全帮你摆平。5.2 连接池参数与 XA 的事务边界第二个坑是连接池配置。XA 模式下分支事务在执行期间必须一直占着一个数据库连接所以连接池的最小连接数、最大连接数、连接空闲超时这些参数都要跟着你的事务并发量来重新评估。我遇到过一个问题业务高峰期订单服务的连接池最大连接数只有 50但全局事务并发量超过了 50导致后面进来的请求拿不到数据库连接直接报CannotGetJdbcConnectionException。表面看是连接池不够根本原因是 XA 模式把连接持有时间拉长了本来一个请求只需要 10 毫秒的连接占用现在因为全局事务等待远程调用连接被占用了 1 秒甚至更多。解决方式分两步。第一步把连接池最大连接数调大至少按照预估的“全局事务并发数 × 每个全局事务内的数据库连接数”来算。第二步也是更重要的一步尽量缩减全局事务内部的操作范围把远程调用挪到事务外面或者用本地消息表的方式替代强一致的实时调用。还有连接池的空闲回收时间也要注意。如果连接的maxIdleTime设置得太短XA 分支事务执行期间连接被连接池误回收会导致分支事务状态和实际数据库连接状态不一致后面做二阶段提交时找不到对应的 XA 分支报错信息通常是XAER_NOTA。5.3 全局事务超时和悬挂分支第三个高频问题就是超时。SEATA 的GlobalTransactional有超时时间配置默认是 60 秒。如果全局事务超过这个时间没有完成二阶段TC 会强制发起回滚。但这里有个隐患如果一阶段的某个分支 SQL 执行到了 “prepare” 阶段但全局事务超时回滚时数据库端的 XA 事务还没有被正确清理就会留下一个悬挂的 XA 分支事务占着锁不释放后续所有访问这些行的事务都会被阻塞。遇到这种情况排查方法是在数据库里执行SELECT * FROM information_schema.innodb_trx WHERE trx_state RUNNING;如果能看到长时间运行的事务并且trx_query为空那大概率就是一个悬挂的 XA 分支。处理办法是针对性的 XA 回滚或者事务 kill但根本的解决方式还是调整超时参数并把事务里的耗时操作优化掉。seata.tm.timeout这个参数值得你重点关注。我一般会把它设成 30 秒左右宁可让事务早点判定超时回滚也别让它拖到数据库连接和锁都被耗尽。但是也别设得太短否则正常的慢 SQL 还没执行完就被强制回滚了。5.4 常见问题速查表我把实战中比较高频的报错和排查思路整理成了一张表方便你直接对照报错信息可能原因排查思路NotFoundException: Branch transaction not found分支事务注册失败xid 没传递到下游服务检查 Feign 调用时xid是否通过请求头传递检查 SEATA 版本确认用的是 SEATA 内置的 feign 拦截器XAER_NOTA: The XID is not valid数据库连接被回收或 XA 分支事务被中断检查连接池空闲回收时间检查全局事务是否超时被强制回滚CannotGetJdbcConnectionException连接池连接数不足调整连接池参数缩减全局事务内操作范围把远程调用移出事务io.seata.common.exception.FrameworkException: No available serviceSEATA 客户端连不上服务端检查注册中心地址、服务端 grouplist 配置、网络连通性Incorrect String Value或字符集报错建表字符集与连接字符集不一致统一数据库、表、连接 URL 的字符集配置UndoLog table doesnt exist误用了 AT 模式的undo_log表确认当前用的是 XA 模式XA 模式不需要建undo_log表检查数据源代理类是否为DataSourceProxyXATransaction rolled back but branch transaction not found分支事务已完成或从未注册成功在服务端日志查全局事务坐标对比 TM 和 RM 的日志时间线5.5 排查工具和通用方法论XA 模式排查问题单看日志是不够的。我自己的方法论是“三层检查法”。第一层看全局事务坐标。先看 TM 日志找到xid再在 SEATA 服务端日志里搜这个xid看全局事务的创建、分支注册、二阶段决策事件是否完整。如果某一环缺失说明对应环节没有正确参与事务。第二层看数据库事务视图。用information_schema.innodb_trx查当前有哪些事务在运行用performance_schema.data_locks查锁的持有情况定位是不是有事务长期占着资源不放。第三层看网络链路传递。xid从订单服务传到库存服务依赖io.seata.core.context.RootContext和 Feign/RestTemplate 的拦截器。如果xid没传递过去下游就是普通本地事务全局一致性直接失效。SEATA 自带的SeataFeignInterceptor一般会在引入依赖后自动生效但如果你自定义了 Feign 的RequestInterceptor要保证不会把tx-xid这个请求头过滤掉。三层检查法配合一张时间线记录表排查效率会快很多。不要一上来就看业务代码XA 模式的坑大部分都在中间件链路和数据库层面业务代码反而是最简单的部分。6. 一个容易忽略的细节XA 模式与事务传播行为写 XA 模式代码时我见过不少同事在同一个服务里叠加GlobalTransactional和Transactional结果事务行为变得不可控。这里面的核心是事务传播行为的设置。Spring 默认的Transactional传播行为是REQUIRED如果外层已经有事务内层就加入外层事务。但 SEATA 的GlobalTransactional和 Spring 的Transactional是两套独立的事务管理体系。GlobalTransactional开启的是 SEATA 全局事务Transactional开启的是数据库本地事务。XA 模式下SEATA 会通过代理数据源把 Spring 管理的本地事务纳入全局 XA 分支但这个过程要求你数据源代理配置正确并且事务传播行为不能乱改。我遇到过一个比较隐蔽的问题在同一个类里一个方法加了GlobalTransactional内部调用了另一个加了Transactional(propagation Propagation.NOT_SUPPORTED)的方法。NOT_SUPPORTED会让本地事务挂起以非事务方式运行结果就是这个方法里的 SQL 不在 XA 分支事务内全局事务管理不到它。一旦这个方法里写入了数据全局回滚时这部分数据是回不掉的数据不一致就这么产生了。所以你在设计代码结构时要明确一个原则全局事务方法内部的数据库操作要么不设置特殊传播行为要么统一走REQUIRED。任何独树一帜的传播行为配置都要先想清楚它在 XA 模式下意味着什么。我个人在实际操作中的体会是XA 模式属于那种“原理看起来简单到不好意思写进简历实际跑起来却能把人折腾到怀疑人生”的东西。两阶段提交是一百个字能讲清楚的概念但要让它在分布式环境里稳定工作需要照顾好数据库驱动版本、连接池生命周期、xid 传递、超时策略、分支事务清理这些方方面面。如果你现在正准备在项目里用 SEATA XA我的建议是先拿一个最简单的下单场景完整跑通再逐步叠加并发和故障注入千万别直接套在一个大而全的复杂流程上否则出了问题和玄学猜谜没什么区别。另外再分享一个小技巧在测试环境准备一个脚本来模拟分支事务的“部分成功部分失败”比如订单成功、库存失败然后反复跑观察最终数据状态。这种故障注入的方式能帮你快速验证全局事务的可靠性也能锻炼团队排查问题的能力。XA 模式不是银弹但它在你需要强一致、又能接受较长锁持有时间的场景下确实是一个非常扎实的选项。
返回列表