TCC三阶段核心职责为:Try阶段做业务检查与资源预留(如冻结余额),Confirm阶段执行真正提交(如扣减冻结金额),Cancel阶段释放预留资源(如解冻余额),三者均需幂等且由业务显式实现。

理解 TCC 三阶段的核心职责
Seata 的 TCC 模式不是靠框架自动回滚,而是由业务代码显式实现 Try、Confirm、Cancel 三个方法。关键点在于:
- Try 阶段:做业务检查 + 资源预留(如冻结余额、预占库存),不真正提交,需保证幂等和可回退;
- Confirm 阶段:执行真正提交逻辑(如扣减冻结金额、出库),仅在 Try 成功后调用,必须幂等且不失败(否则无法兜底);
- Cancel 阶段:释放 Try 预留的资源(如解冻余额、释放库存),也必须幂等,用于 Try 成功但 Confirm 失败或全局事务回滚时。
编写带 @TwoPhaseBusinessAction 注解的业务接口
以账户扣款为例,定义一个 TCC 接口并实现三阶段逻辑:
第一步,声明接口(含注解):
@LocalTCC
public interface AccountTccService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirmDeduct", rollbackMethod = "cancelDeduct")
boolean deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
<pre class="brush:php;toolbar:false;">boolean confirmDeduct(BusinessActionContext context);
boolean cancelDeduct(BusinessActionContext context);}
注意:name 是全局唯一动作标识;commitMethod 和 rollbackMethod 必须是 public 方法且参数为 BusinessActionContext;所有方法返回 boolean,false 表示执行失败,触发 Seata 回滚流程。
在实现类中落实幂等、悬挂、空回滚防护
TCC 实际落地最易出错的是异常场景。必须在 Confirm/Cancel 中主动校验状态,避免重复执行或误操作:
- 空回滚:Try 未执行(网络超时/服务宕机),但 Cancel 被调用 → 在 Cancel 中先查“是否有对应 Try 记录”,无则直接返回 true;
- 悬挂:Try 超时失败,但后续又成功入库(因重试或延迟),而 Confirm 已跳过 → 在 Confirm 前查 Try 是否成功,若未预留资源则拒绝执行;
-
幂等:每次 Confirm/Cancel 执行前,用
context.getXid()+context.getActionName()+ 业务主键生成唯一事务记录,插入 DB 或 Redis 做去重判断。
例如 Cancel 中加校验:
// 查询是否已执行过 Try(比如查 t_account_freeze 表)String xid = context.getXid();
String userId = (String) context.getActionContext("userId");
if (!freezeMapper.existsByXidAndUserId(xid, userId)) {
return true; // 空回滚,直接放行
}
在全局事务入口开启 @GlobalTransactional
TCC 分支逻辑不会自动加入分布式事务,必须由发起方用 @GlobalTransactional 标记入口方法,Seata 才会协调各 TCC 参与者:
@GlobalTransactional
public void transfer(String fromUserId, String toUserId, BigDecimal amount) {
// 调用第一个 TCC:扣出方
accountTccService.deduct(fromUserId, amount);
// 调用第二个 TCC:充值方(同理实现 try/increase/rollback)
accountTccService.increase(toUserId, amount);
// 中间任意一步抛异常 → Seata 自动触发所有已执行分支的 Cancel
}
确保所有 TCC 接口调用都在同一个 Spring Bean 方法内,且该方法被 @GlobalTransactional 包裹,否则分支注册失败。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











