分布式事务实战:Seata AT模式与TCC方案对比

微服务架构下,一个业务操作跨多个服务、多个数据库,本地事务管不住全局一致性,这就是分布式事务要解决的问题。分布式事务的方案很多,本文对比最常用的Seata AT模式与TCC模式,给出选型建议和代码示例,供后端开发做技术选型时参考。

分布式事务的三种基础模型

业界主流的分布式事务方案可归为三类:

2PC(两阶段提交):XA协议为代表,强一致,但锁资源时间长、性能差。
TCC(Try-Confirm-Cancel):业务层补偿,无锁,需要三个接口,开发量大。
本地消息表/事务消息:最终一致性,吞吐高,适合不要求实时一致的场景。

另外还有Saga长事务,适合多步骤编排。选型核心看两点:业务对一致性的要求,以及是否允许中间状态被外部看到。

Seata AT模式:自动补偿的无侵入方案

AT模式基于2PC的变体,由框架自动生成补偿SQL,业务代码几乎无侵入。核心流程:

1. 事务管理器(TC)开启全局事务。
2. 分支事务注册到TC,执行本地SQL。
3. 提交前,TC协调各分支做二阶段提交;失败则反向执行补偿SQL回滚。

AT模式依赖全局锁和undo_log表,本地库自动维护undo_log回滚数据。Java接入示例:

@GlobalTransactional(name = "create-order", timeoutMills = 30000)
public void createOrder(OrderDTO dto) {
    orderService.insert(dto);          // 本地事务,自动注册分支
    inventoryService.deduct(dto);      // 跨服务调用
    accountService.deduct(dto.getAmount());
}

只要在入口方法加@GlobalTransactional注解,分支服务接入seata客户端,框架自动完成回滚。数据一致性靠undolog,性能损耗主要在一阶段锁等待和二阶段回滚。

AT模式适用边界与注意事项

AT模式适合读多写少、对性能不太敏感、表结构规整的业务。三个容易踩的坑:

1. 全局锁范围大:高并发下热点行冲突明显,压测时要观察全局锁等待时间。
2. undo_log清理:Seata会定期清理,但要确认表结构兼容。
3. 不支持跨数据库DDL和部分SQL语法:像DDL语句无法生成逆向SQL。

如果业务对吞吐要求极高,或跨服务链路很长,AT模式的锁开销会放大,此时考虑TCC。

TCC模式:业务补偿的强一致方案

TCC把一次事务拆成Try(预留资源)、Confirm(确认提交)、Cancel(取消补偿)三个阶段,全程由业务代码控制,性能远好于2PC,且无全局锁。

public class InventoryServiceTcc {
    @LocalTCC
    @TwoPhaseBusinessAction(name = "deductInventory",
        commitMethod = "confirmDeduct", rollbackMethod = "cancelDeduct")
    public boolean tryDeduct(BusinessActionContext ctx, Long goodsId, int num) {
        // 1. 检查库存
        // 2. 冻结库存(冻结字段 + 减可用)
        return inventoryMapper.freeze(goodsId, num) > 0;
    }
    public boolean confirmDeduct(BusinessActionContext ctx) {
        // 扣减冻结库存,写入正式库存表
        return inventoryMapper.confirm(ctx.getActionContext("goodsId")) > 0;
    }
    public boolean cancelDeduct(BusinessActionContext ctx) {
        // 释放冻结库存
        return inventoryMapper.unfreeze(ctx.getActionContext("goodsId")) > 0;
    }
}

TCC的关键是三个方法都要幂等(重试不会产生重复扣减),且Try阶段要防止数据被并发污染。业务复杂度随接口数量线性增长,事务链路每多一个服务,就要多写一套TCC,维护成本高。

Seata TCC与AT的选型对比

AT:开发量小,框架自动回滚,但全局锁对热点冲突明显,适合中小流量。
TCC:无全局锁、性能可控,但每个分支要写三个方法并保证幂等,适合高并发核心链路(支付、库存)。

没有万能方案,同一系统里不同服务可以混用:核心账户用TCC,普通订单用AT,报表类对账直接用消息队列最终一致性。

分布式事务之外的替代思路

不少场景不需要分布式事务:

1. 本地消息表:业务和消息写入同一库,消费方再拉取,最终一致。
2. RocketMQ事务消息:半消息+本地事务回调,可靠投递。
3. 事件驱动+幂等消费:把跨服务操作改成发事件,消费方幂等处理,系统解耦更彻底。

设计上能拆分就拆分,把事务边界控制在单个服务内,是成本最低的”分布式事务”。不得不跨服务时,再按一致性强度、吞吐、开发成本三要素选型,并把幂等设计在全局做扎实。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-fang-an/

赞 (0)
小编小编
上一篇 2026年9月2日
下一篇 2026年9月2日

相关推荐

分布式事务实战:Seata AT模式与TCC方案落地对比

微服务架构下,一次业务操作跨多个服务写库,本地事务就不够用了,分布式事务成为后端开发的必修课。常见的落地手段有Seata AT模式、TCC、本地消息表、事务消息(RocketMQ)。本文对比这几种方案的使用场景,重点拆解Seata AT和TCC的配置与注意事项,给出选型结论和代码示例。

一、分布式事务的几种方案对比

  • Seata AT模式:对业务代码侵入最小,框架自动生成undo_log,适合大多数增删改场景。
  • Seata TCC:需要业务实现Try/Confirm/Cancel三方法,适合资金、库存等强一致性场景。
  • 本地消息表+定时任务:最终一致,简单可靠,适合订单与积分这类异步场景。
  • RocketMQ事务消息:事务与消息同生命周期,替代本地消息表的主流方案。

选型结论:链路不长、一致性要求高用AT;涉及资金、需要资源预留用TCC;可以接受异步最终一致用事务消息。不要为了分布式事务把架构复杂化,能用本地事务就不要拆分。

二、Seata AT模式部署与接入

Seata Server(TC)独立部署,业务服务接入RM和TM。AT模式靠全局锁+undo_log实现。以Spring Boot 3为例:

# application.yml
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_test_tx_group
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848

业务侧:

@GlobalTransactional(name = "create_order", timeoutMills = 30000)
public void createOrder(OrderDTO dto) {
    orderMapper.insert(dto.toOrder());
    stockService.deduct(dto.getSkuId(), dto.getCount());
    accountService.debit(dto.getUserId(), dto.getAmount());
}

AT模式注意事项:

  • 业务表必须有主键,undo_log表(每库一张)由seata-server初始化。
  • 不支持DDL和部分函数,SELECT for update要慎用。
  • 跨库跨事务组时,全局锁等待超时会造成热点行阻塞,配置defaultGlobalLockTimeout。

三、TCC模式落地示例

TCC把一笔事务拆成Try(预留资源)、Confirm(确认)、Cancel(回滚)。以扣库存为例:

public class InventoryServiceImpl implements InventoryTccService {
    @TwoPhaseBusinessAction(name = "deductStock", commitMethod = "confirm", rollbackMethod = "cancel")
    public boolean tryDeduct(BusinessActionContext ctx, Long skuId, int count) {
        // Try:冻结库存,不真正扣减
        return inventoryMapper.freeze(skuId, count) > 0;
    }
    public boolean confirm(BusinessActionContext ctx) {
        // Confirm:把冻结库存转成实际扣减
        return inventoryMapper.freeze2Deduct(ctx.getBusinessKey()) > 0;
    }
    public boolean cancel(BusinessActionContext ctx) {
        // Cancel:解冻
        return inventoryMapper.unfreeze(ctx.getBusinessKey()) > 0;
    }
}

TCC的难点在幂等和悬挂:Confirm/Cancel可能重复执行,必须做幂等;Cancel在Try失败或超时时也要能补偿,预留的冻结资源要能正确释放。

四、事务消息:异步最终一致的替代方案

不追求实时强一致的下单、积分、短信场景,用RocketMQ事务消息更简单:

TransactionMQProducer producer = new TransactionMQProducer("tx_producer");
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 本地事务:写订单表
        return orderMapper.insert((Order) arg) > 0
            ? LocalTransactionState.COMMIT_MESSAGE
            : LocalTransactionState.ROLLBACK_MESSAGE;
    }
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查:查订单是否存在
        return orderMapper.existsByMsgId(msg.getKeys())
            ? LocalTransactionState.COMMIT_MESSAGE
            : LocalTransactionState.ROLLBACK_MESSAGE;
    }
});

事务消息把“本地事务提交”和“消息发送”绑定成原子操作,避免了本地消息表多一张表的心智负担。

五、选型落地建议

分布式事务没有银弹:AT模式适合从单体拆微服务的过渡期,改造成本低;TCC适合资金类和库存强一致性;事务消息适合能容忍秒级延迟的场景。性能上AT和TCC都会放大数据库压力,压测时要把Seata的事务上下文和锁等待时间算进去。上线前一定要做故障演练:模拟下游超时、重复调用、Cancel重试,验证整个链路不回滚错数据。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-yu-tcc-fang-an/

赞 (0)
小编小编
上一篇 2026年8月21日
下一篇 2026年8月21日

相关推荐