Spring Boot微服务为什么需要分布式事务
单体应用拆分为微服务后,原本一个本地事务就能搞定的业务操作,现在跨了多个服务的数据库。比如电商下单场景:订单服务创建订单、库存服务扣减库存、账户服务扣减余额——三个操作必须全部成功或全部回滚。用本地事务做不到,因为每个服务连的是不同的数据库实例。
Seata是蚂蚁金服开源的分布式事务中间件,AT模式是其核心方案,对业务代码侵入最小:只需在方法上加@GlobalTransactional注解,Seata通过拦截SQL自动生成回滚日志(undo log),在任意分支事务失败时自动补偿。和TCC模式需要手动写Try/Confirm/Cancel三个方法不同,AT模式的改造成本低得多。
Seata AT模式的工作原理
AT模式的核心流程分两阶段:
第一阶段(业务执行+生成undo log):Seata拦截业务SQL,在执行前查询数据旧值,执行后查询数据新值,将旧值+新值组合生成undo log记录写入seata_undo_log表,然后执行业务SQL并提交本地事务。此时本地事务已提交,其他服务可以看到中间状态。
第二阶段(提交或回滚):如果全局事务决议为提交,异步删除undo log(一阶段已提交,不需要再做任何事);如果决议为回滚,读取undo log中的旧值,生成反向SQL(把新值改回旧值)执行补偿,然后删除undo log。
关键设计点:一阶段就提交了本地事务,释放了数据库锁,因此不会长时间持有锁导致性能问题。代价是数据有短暂的中间状态可见窗口,但通过全局锁机制保证不会出现脏写——其他全局事务在修改同一行数据时必须先获取全局锁,获取失败则等待或回滚。
Spring Boot 3.x集成Seata实战步骤
第一步:部署Seata Server
Seata Server是事务协调器(TC),所有微服务作为事务参与者(RM/TM)向TC注册。使用Docker快速部署:
# docker-compose.seata.yml
version: "3"
services:
seata-server:
image: seataio/seata-server:2.2.0
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=seata-server
- STORE_MODE=db
volumes:
- ./seata-config:/seata-server/config
Seata Server的存储模式建议用db(数据库存储事务日志),生产环境不推荐file模式。配置文件application.yml:
seata:
config:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
store:
mode: db
db:
datasource: druid
db-type: mysql
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true
user: seata
password: seata_pwd
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
第二步:微服务引入Seata依赖
每个参与分布式事务的Spring Boot服务都需要添加依赖和配置:
<!-- pom.xml -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Spring Boot 3.x 需要额外引入 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-autoconfigure-client</artifactId>
<version>2.2.0</version>
</dependency>
服务端配置application.yml:
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
registry:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
第三步:每个微服务的数据库建undo_log表
-- 每个参与分布式事务的数据库都要建这张表
CREATE TABLE IF NOT EXISTS `seata_undo_log` (
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚信息',
`log_status` INT(11) NOT NULL COMMENT '日志状态',
`log_created` DATETIME(6) NOT NULL COMMENT '创建时间',
`log_modified` DATETIME(6) NOT NULL COMMENT '修改时间',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT undo log表';
第四步:在业务方法上加注解
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setCommodityCode(dto.getCommodityCode());
order.setCount(dto.getCount());
order.setMoney(dto.getMoney());
order.setStatus("CREATING");
orderMapper.insert(order);
// 2. 远程调用扣减库存
stockClient.deduct(dto.getCommodityCode(), dto.getCount());
// 3. 远程调用扣减余额
accountClient.debit(dto.getUserId(), dto.getMoney());
// 4. 更新订单状态
order.setStatus("COMPLETED");
orderMapper.updateById(order);
return order;
}
}
只要任意一步抛异常,Seata TC会通知所有参与的RM读取undo log执行回滚。
AT模式的踩坑与应对方案
坑1:undo_log表没建导致静默失败
如果某个微服务的数据库没有建undo_log表,Seata不会报错阻止启动,而是在运行时写undo失败,回滚时找不到日志,全局事务部分回滚部分未回滚,数据不一致。部署检查清单中必须包含undo_log表的确认。
坑2:全局锁超时
两个全局事务同时修改同一行数据时,后到的事务需要等待前一个事务释放全局锁。默认等待时间30秒,超时后抛异常。高并发场景下这个超时值需要调大,同时优化业务逻辑减少跨事务的热点行争用:
seata:
client:
rm:
lock:
retry-interval: 50 # 获取全局锁重试间隔50ms
retry-times: 120 # 重试120次(6秒)
async-commit-buffer-limit: 10000
坑3:SQL兼容性问题
Seata AT模式通过拦截SQL生成undo log,但不是所有SQL语法都支持。不支持的操作包括:INSERT时不带列名(INSERT INTO t VALUES(...)),多表UPDATE,使用数据库函数作为列值(如NOW()),LIMIT子句等。写业务SQL时要注意这些限制:
// 不支持的写法
INSERT INTO order_table VALUES(1, 'test', NOW());
// 改为
INSERT INTO order_table (id, name, created_at) VALUES(1, 'test', '2026-07-28 10:00:00');
坑4:Spring Boot 3.x的Bean初始化顺序
Spring Boot 3.x使用Spring Framework 6,Bean的初始化顺序和2.x有差异。如果Seata的数据源代理没有在MyBatis/JPA之前生效,AT模式拦截不到SQL。确保数据源代理配置正确:
@Bean
@Primary
public DataSource dataSource(DataSource originalDataSource) {
// Seata代理原始数据源
return new DataSourceProxy(originalDataSource);
}
确认代理生效的方法:启动日志中搜索DataSourceProxy相关输出,或通过断点确认注入的DataSource类型。
AT模式与TCC/Saga的选型决策
AT模式适合90%的分布式事务场景,但有边界:如果业务操作不是SQL(比如调用第三方API、发送消息等),AT无法自动生成undo log,这时需要用TCC模式手动写补偿逻辑。如果业务流程很长(涉及5个以上服务),AT模式的全局锁持有时间长会影响并发,Saga模式更适合长流程——每个步骤只做正向操作,失败时反向执行补偿链。
选型建议:SQL操作为主、流程3-5步以内用AT;涉及非SQL操作用TCC;超长流程(如旅行预订涉及航班+酒店+租车+保险)用Saga。不要在一个项目中混用多种模式,增加维护复杂度。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot3x-ji-cheng-seata-fen-bu-shi-shi-wu-at-mo-shi-cai/