面试知识库
困难

TCC事务模式#

一句话答案#

TCC 三阶段:Try(预留资源)→Confirm(确认提交)→Cancel(取消释放),灵活高性能但业务侵入大。

核心要点

TCC(Try-Confirm-Cancel): 一种业务层面的分布式事务解决方案,将每个操作分为三个步骤,由业务代码自己实现。

Try(尝试):预留/冻结资源,做业务检查
  → 不真正执行业务,只是资源预留

Confirm(确认):确认提交,执行真正的业务操作,使用 Try 阶段预留的资源
  → 不做任何业务检查,直接使用预留资源

Cancel(取消):释放 Try 阶段预留的资源
  → 回滚 Try 阶段的操作
plaintext

转账案例(A 向 B 转 100 元):

Try 阶段:
  账户 A:检查余额 >= 100 → 冻结 100 元(frozen_amount += 100)
  账户 B:创建待入账记录(pending_amount += 100)

Confirm 阶段(所有 Try 成功后执行):
  账户 A:扣减冻结金额(balance -= 100, frozen_amount -= 100)
  账户 B:确认入账(balance += 100, pending_amount -= 100)

Cancel 阶段(任一 Try 失败后执行):
  账户 A:解冻金额(frozen_amount -= 100)
  账户 B:删除待入账记录(pending_amount -= 100)
plaintext

TCC 实际使用的三大注意事项:

1. 幂等性(最关键): Confirm 和 Cancel 必须是幂等的。网络超时导致重试时,同一操作可能被执行多次。

// 幂等实现方案:用事务状态表记录执行状态
public void confirm(String txId) {
    // 先检查是否已经 Confirm 过
    TccRecord record = tccRecordMapper.selectByTxId(txId);
    if (record.getStatus() == CONFIRMED) {
        return; // 已确认过,直接返回(幂等)
    }
    // 执行 Confirm 逻辑...
    tccRecordMapper.updateStatus(txId, CONFIRMED);
}
java

2. 空回滚: Cancel 被调用时,Try 可能还没执行(网络延迟导致 Try 请求还没到)。Cancel 必须能处理这种情况——检测到 Try 未执行时,直接返回成功。

public void cancel(String txId) {
    TccRecord record = tccRecordMapper.selectByTxId(txId);
    if (record == null) {
        // Try 还没执行过 → 空回滚 → 插入一条记录标记已回滚
        tccRecordMapper.insert(txId, CANCELLED);
        return;
    }
    // 正常 Cancel 逻辑...
}
java

3. 悬挂(Suspension): 空回滚之后,迟到的 Try 请求才到达并执行——此时资源被预留但永远不会被 Confirm 或 Cancel(因为 Cancel 已经执行过了)。解决方案:Try 阶段先检查是否已经 Cancel 过,如果已 Cancel 则拒绝 Try。

public boolean tryAction(String txId) {
    TccRecord record = tccRecordMapper.selectByTxId(txId);
    if (record != null && record.getStatus() == CANCELLED) {
        return false; // 已经 Cancel 过了,拒绝 Try(防悬挂)
    }
    // 正常 Try 逻辑...
    tccRecordMapper.insert(txId, TRYING);
    return true;
}
java

TCC 优缺点:

优点缺点
无资源锁定,性能高业务侵入性大(每个操作都要写 Try/Confirm/Cancel)
最终一致性保证开发成本高(要处理幂等、空回滚、悬挂)
适合高并发金融场景维护成本高(业务变更需要同步修改三个方法)
面试回答(2分钟版)

TCC 是一种业务层面的分布式事务方案,把每个操作拆成 Try、Confirm、Cancel 三步。Try 阶段做资源预留,比如转账时冻结金额但不真正扣款;Confirm 阶段在所有 Try 成功后确认提交,真正执行扣款和入账;Cancel 阶段在任一 Try 失败后释放预留资源。和 2PC 相比,TCC 在 Try 阶段就提交了本地事务,不会长时间持锁,性能高很多,适合高并发的金融场景。但 TCC 的代价是业务侵入性大,每个操作都要手写三个方法,而且必须处理三个棘手问题:幂等性——Confirm 和 Cancel 可能因网络重试被多次调用,必须保证重复执行结果一致;空回滚——Cancel 被调用时 Try 可能还没执行到,要能正确处理;悬挂——空回滚之后迟到的 Try 请求到达,必须拒绝执行防止资源被永久锁定。实际开发中一般用 Seata TCC 模式或 ByteTCC 等框架来简化实现。

追问与易错

追问方向:

  • “空回滚和悬挂问题?怎么解决?”→ 空回滚:Cancel 时 Try 未执行,需判断 Try 状态直接返回;悬挂:Cancel 后 Try 迟到,Try 需检查 Cancel 是否已执行并拒绝;统一用事务状态表记录各阶段执行状态
  • “Confirm/Cancel 失败怎么办?”→ 框架自动重试(Confirm/Cancel 必须幂等),重试上限后告警人工介入,最终靠对账兜底
  • “TCC 比 2PC 好在哪?”→ TCC 在 Try 阶段就提交本地事务不长时间持锁,性能远优于 2PC;且 TCC 在业务层实现更灵活,不依赖数据库的 XA 支持

易错点:

  • ❌ TCC 就是代码级的 2PC——TCC 一阶段就提交
  • ❌ 忘记处理空回滚