在单体应用里,事务交给数据库就行,一个@Transactional注解几乎能解决所有一致性问题。但一旦系统拆成多个微服务,订单服务扣库存、账户服务扣余额、积分服务加积分,这些操作分散在不同库不同实例里,本地事务就无能为力了。Seata就是为解决这个问题而生的开源框架,它通过全局事务协调器TC、事务管理器TM和资源管理器RM三个角色,把多个分支事务纳入一个全局事务统一提交或回滚。这篇文章把Seata的四种模式怎么选、怎么接、以及哪些坑要避开,一次性讲清楚。

四种事务模式怎么选:先看业务再动手
Seata支持AT、TCC、Saga、XA四种模式,很多刚接触的人容易一头雾水,不知道该用哪个。其实选型的核心判断依据就三条:是否愿意改代码、业务对性能的要求、以及能否接受中间状态的短暂不一致。
AT模式是Seata的主推模式,对业务几乎零侵入。它在一阶段就把本地事务提交了,同时把修改前后的数据镜像存进undo_log表,二阶段回滚时靠这份镜像做反向补偿。优点是接入成本最低,业务代码完全不用感知分布式事务的存在。代价是它追求的是最终一致,且依赖全局锁防止脏写,高并发热点行场景下性能会受影响。
TCC模式需要为每个业务手动实现Try、Confirm、Cancel三个方法,侵入性强,开发量大,但资源锁定完全由业务自己控制,粒度最细,性能也最好。适合对并发要求极高的核心场景,比如资金账户、抢购扣减。Saga模式则适合长流程、长事务的场景,比如订单跨越多个外部系统、甚至调用第三方接口,无法用短事务包裹时,用状态机编排正向服务和补偿服务。XA模式依赖数据库原生XA协议,强一致但性能最差,锁持有时间长,一般只在金融强一致且并发不高的内部系统里使用。
一张表快速对照
| 模式 | 侵入性 | 一致性 | 性能 | 典型场景 |
|---|---|---|---|---|
| AT | 无侵入 | 最终一致 | 中 | 一般微服务业务 |
| TCC | 高 | 最终一致 | 高 | 资金、库存等高并发 |
| Saga | 中 | 最终一致 | 高 | 长流程跨系统编排 |
| XA | 低 | 强一致 | 低 | 金融强一致低并发 |
一个实用的建议是:默认选AT,遇到热点行竞争再局部换TCC,长流程业务用Saga,强一致刚需才考虑XA。不要一上来就全用TCC,三个方法 × N个服务的工作量会让团队崩溃。
接入步骤详解:从Server搭建到业务生效
接入Seata分两大部分:先部署Seata Server(TC),再让业务服务作为TM/RM接入。以AT模式为例,先下载seata-server,配置注册中心和存储模式。生产环境建议把全局事务会话存到数据库或Redis,不要用默认的file模式,否则TC单点重启会丢全局事务状态。
-- Seata Server 需要的数据库表,MySQL为例 CREATE TABLE `global_table` ( `xid` varchar(128) NOT NULL, `transaction_id` bigint DEFAULT NULL, `status` int NOT NULL, `application_id` varchar(32) DEFAULT NULL, `transaction_name` varchar(128) DEFAULT NULL, `timeout` int DEFAULT NULL, `begin_time` bigint DEFAULT NULL, PRIMARY KEY (`xid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 类似地还需要 branch_table、lock_table、distributed_lock
业务侧每个参与全局事务的数据库,都要创建undo_log表,这是AT模式回滚的依据,漏建的话回滚直接失败。
CREATE TABLE `undo_log` ( `id` bigint auto_increment PRIMARY KEY, `branch_id` varchar(32) NOT NULL, `xid` varchar(128) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int NOT NULL, `log_created` datetime(6) NOT NULL, `log_modified` datetime(6) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
然后是应用配置。引入seata-spring-boot-starter依赖,在application.yml里指定事务组、TC地址和代理数据源。注意数据源一定要交给Seata代理,否则RM无法拦截SQL生成undo_log,这是新手最常见的接入失败原因。
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default # 映射到TC集群名
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
application: seata-server
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
data-source-proxy-mode: AT
最后在全局事务的发起方加上@GlobalTransactional注解,下游分支服务通过RPC链路自动传播XID,各分支的本地事务就会被纳入全局管理。这里要强调一点:@GlobalTransactional只标在入口方法上,下游服务不要再重复标注,否则会开启新的全局事务,语义就乱了。
常见坑点与避坑建议
第一个大坑是全局锁竞争导致的性能问题。AT模式在本地事务提交前,必须先拿到被修改行的全局锁,如果两个全局事务同时改同一行,后到的会自旋等待甚至回滚。热点数据场景下这个竞争会非常明显。规避方式有几种:缩小事务范围,把不必要的操作移出全局事务;调低client.rm.lock.retryTimes减少自旋;或者干脆把这类热点操作改造成TCC,用业务字段做预留。
第二个坑是脏写导致回滚失败。AT的回滚依赖undo_log镜像,如果某个全局事务进行中,另一个不走Seata的裸SQL直接改了同一行数据,回滚时镜像与当前数据对不上,Seata会记录回滚失败并挂起该分支。所以接入Seata后,凡是涉及相关表的写操作,必须都走Seata代理的数据源,不能存在旁路写。
第三个坑是参数配置类问题。defaultGlobalTransactionTimeout默认60秒,长流程业务容易超时导致全局事务被TC主动回滚,而业务还在继续执行,造成数据错乱。要根据实际业务最大耗时设置。另外事务组映射、注册中心命名空间要严格一致,事务组找不到TC集群时报的错经常很隐晦,表现为启动正常但一发起事务就报can not get cluster name。还有一点,undo_log表的清理由Seata自动完成,但如果频繁出现undo_log堆积,多半是回滚异常挂起了分支事务,要去查TC日志定位原因,不要手动乱删。
最后提一句高可用。TC Server至少部署两个实例组成集群,注册到Nacos并配置db存储模式,业务侧通过事务组映射自动发现TC节点。TC短暂不可用时,正在进行的全局事务会失败重试,所以还要配合业务侧的幂等和重试机制,双保险才能真正保证数据不出问题。分布式事务框架解决的是协调问题,异常兜底永远要靠业务自己设计好补偿与幂等。
总体来说,Seata的接入门槛不高,难的是选型判断和细节调优。按业务场景选对模式、管好数据源代理、避开全局锁和脏写的坑,Seata就能稳定支撑绝大多数微服务场景的数据一致性需求。