写点什么

分布式事务在分片场景下,TCC 和 Seata 到底怎么选?一线实战全解析!

  • 2025-08-08
    湖北
  • 本文字数:4463 字

    阅读完需:约 15 分钟

一、分片场景下的分布式事务,真的是"地狱级"难题

还记得第一次在分片数据库上实现分布式事务时,我天真地以为用个简单的两阶段提交就搞定了。结果上线后,事务要么提交失败,要么回滚不完整、


今天我们就来聊聊,在分片场景下,TCC 和 Seata 到底该怎么选?分布式事务又该怎么实现?这些坑,我踩过,你也别踩了!



二、分片场景下的分布式事务,为什么这么难?

1. 分片场景的特殊性

在分片数据库中,数据被分散到多个节点上,传统的 ACID 事务很难保证。比如:


-- 用户表在分片1UPDATE users SET balance = balance - 100 WHERE user_id = 123;
-- 订单表在分片2 INSERT INTO orders (user_id, amount) VALUES (123, 100);
复制代码


这两个操作跨分片,传统事务无法保证原子性。

2. 常见的"翻车现场"

场景 1:部分提交,部分回滚


// 用户扣款成功,订单创建失败try {    // 分片1:扣款成功    userService.deductBalance(userId, amount);        // 分片2:订单创建失败    orderService.createOrder(order);        // 结果:钱扣了,订单没创建,数据不一致!} catch (Exception e) {    // 回滚逻辑复杂,容易遗漏}
复制代码


场景 2:网络分区导致的不一致


  • 分片 1 事务提交成功

  • 分片 2 网络超时,事务状态未知

  • 系统重启后,数据状态混乱



三、TCC 模式:手动补偿的"硬核"方案

1. TCC 是什么?

TCC(Try-Confirm-Cancel)是一种手动补偿的分布式事务模式:


  • Try 阶段:资源预留,检查并预留资源

  • Confirm 阶段:确认执行,真正执行业务逻辑

  • Cancel 阶段:取消执行,释放预留的资源

2. TCC 在分片场景下的实现

用户服务 TCC 实现:


@Servicepublic class UserTccService {        // Try阶段:冻结用户余额    @Transactional    public boolean tryDeductBalance(Long userId, BigDecimal amount) {        User user = userMapper.selectById(userId);        if (user.getBalance().compareTo(amount) < 0) {            throw new RuntimeException("余额不足");        }                // 冻结余额,而不是直接扣款        user.setFrozenAmount(user.getFrozenAmount().add(amount));        userMapper.updateById(user);        return true;    }        // Confirm阶段:确认扣款    @Transactional    public boolean confirmDeductBalance(Long userId, BigDecimal amount) {        User user = userMapper.selectById(userId);        // 真正扣款        user.setBalance(user.getBalance().subtract(amount));        user.setFrozenAmount(user.getFrozenAmount().subtract(amount));        userMapper.updateById(user);        return true;    }        // Cancel阶段:取消扣款    @Transactional    public boolean cancelDeductBalance(Long userId, BigDecimal amount) {        User user = userMapper.selectById(userId);        // 解冻余额        user.setFrozenAmount(user.getFrozenAmount().subtract(amount));        userMapper.updateById(user);        return true;    }}
复制代码


订单服务 TCC 实现:


@Servicepublic class OrderTccService {        // Try阶段:预创建订单    @Transactional    public boolean tryCreateOrder(Order order) {        // 检查库存等前置条件        if (!checkInventory(order.getProductId(), order.getQuantity())) {            throw new RuntimeException("库存不足");        }                // 预创建订单,状态为"待确认"        order.setStatus("PENDING");        orderMapper.insert(order);        return true;    }        // Confirm阶段:确认订单    @Transactional    public boolean confirmCreateOrder(Long orderId) {        Order order = orderMapper.selectById(orderId);        order.setStatus("CONFIRMED");        orderMapper.updateById(order);        return true;    }        // Cancel阶段:取消订单    @Transactional    public boolean cancelCreateOrder(Long orderId) {        Order order = orderMapper.selectById(orderId);        order.setStatus("CANCELLED");        orderMapper.updateById(order);        return true;    }}
复制代码

3. TCC 的优缺点

优点:


  • 性能好,不需要全局锁

  • 可以精确控制事务边界

  • 支持长事务


缺点:


  • 开发成本高,需要手动实现三个接口

  • 业务侵入性强

  • 补偿逻辑复杂,容易出错



四、Seata 模式:自动补偿的"懒人"方案

1. Seata 是什么?

Seata 是阿里开源的分布式事务框架,提供了 AT、TCC、SAGA、XA 四种模式,在分片场景下主要使用 AT 模式。

2. Seata AT 模式在分片场景下的实现

配置 Seata:


# application.ymlseata:  enabled: true  application-id: ${spring.application.name}  tx-service-group: my_test_tx_group  enable-auto-data-source-proxy: true  data-source-proxy-mode: AT  service:    vgroup-mapping:      my_test_tx_group: default    grouplist:      default: 127.0.0.1:8091
复制代码


业务代码实现:


@Servicepublic class OrderService {        @GlobalTransactional    public void createOrder(OrderRequest request) {        // 分片1:扣减用户余额        userService.deductBalance(request.getUserId(), request.getAmount());                // 分片2:创建订单        orderService.createOrder(request.getOrder());                // 分片3:更新库存        inventoryService.updateStock(request.getProductId(), request.getQuantity());    }}
复制代码


Seata 自动补偿机制:


// Seata会自动生成补偿SQL// 原始SQL:UPDATE users SET balance = balance - 100 WHERE id = 123// 补偿SQL:UPDATE users SET balance = balance + 100 WHERE id = 123
复制代码

3. Seata 的优缺点

优点:


  • 开发简单,只需要加注解

  • 自动生成补偿逻辑

  • 支持多种事务模式


缺点:


  • 性能相对较低,需要全局锁

  • 对数据库有侵入性(需要 undo_log 表)

  • 不适合长事务



五、分片场景下的选择策略

1. 什么时候选 TCC?

适用场景:


  • 对性能要求极高的场景

  • 业务逻辑复杂,需要精确控制

  • 长事务场景

  • 对数据库侵入性要求高的场景


示例:


// 电商下单场景,涉及多个分片@GlobalTransactionalpublic void placeOrder(OrderRequest request) {    // 用户服务(分片1)    userTccService.tryDeductBalance(request.getUserId(), request.getAmount());        // 订单服务(分片2)    orderTccService.tryCreateOrder(request.getOrder());        // 库存服务(分片3)    inventoryTccService.tryDeductStock(request.getProductId(), request.getQuantity());        // 如果都成功,进入Confirm阶段    // 如果有失败,进入Cancel阶段}
复制代码

2. 什么时候选 Seata?

适用场景:


  • 快速开发,对性能要求不高的场景

  • 业务逻辑相对简单

  • 短事务场景

  • 团队对分布式事务经验不足


示例:


// 简单的转账场景@GlobalTransactionalpublic void transfer(Long fromUserId, Long toUserId, BigDecimal amount) {    // 分片1:扣减转出用户余额    userService.deductBalance(fromUserId, amount);        // 分片2:增加转入用户余额    userService.addBalance(toUserId, amount);}
复制代码



六、实战案例:电商订单系统的分布式事务设计

需求分析:

  • 订单系统采用分片架构

  • 涉及用户余额、订单创建、库存扣减

  • 要求高可用、高性能

  • 需要支持事务回滚

方案设计:

方案 1:TCC 模式(推荐)


@Servicepublic class OrderTccService {        @GlobalTransactional    public void placeOrder(OrderRequest request) {        // Try阶段:资源预留        userTccService.tryDeductBalance(request.getUserId(), request.getAmount());        orderTccService.tryCreateOrder(request.getOrder());        inventoryTccService.tryDeductStock(request.getProductId(), request.getQuantity());                // 如果Try阶段都成功,自动进入Confirm阶段        // 如果有失败,自动进入Cancel阶段    }}
复制代码


优点:


  • 性能好,支持高并发

  • 可以精确控制事务边界

  • 支持复杂的业务逻辑


缺点:


  • 开发成本高

  • 需要手动实现补偿逻辑


方案 2:Seata AT 模式


@Servicepublic class OrderService {        @GlobalTransactional    public void placeOrder(OrderRequest request) {        // 直接执行业务逻辑,Seata自动处理事务        userService.deductBalance(request.getUserId(), request.getAmount());        orderService.createOrder(request.getOrder());        inventoryService.deductStock(request.getProductId(), request.getQuantity());    }}
复制代码


优点:


  • 开发简单

  • 自动生成补偿逻辑

  • 学习成本低


缺点:


  • 性能相对较低

  • 对数据库有侵入性



七、分片场景下的最佳实践

1. 设计原则:

事务粒度控制:


  • 尽量缩小事务范围

  • 避免跨分片的长事务

  • 合理设计事务边界


补偿策略设计:


// 幂等性设计@Transactionalpublic boolean deductBalance(Long userId, BigDecimal amount, String txId) {    // 检查是否已经处理过    if (isProcessed(txId)) {        return true;    }        // 执行业务逻辑    User user = userMapper.selectById(userId);    user.setBalance(user.getBalance().subtract(amount));    userMapper.updateById(user);        // 记录处理状态    recordProcessed(txId);    return true;}
复制代码

2. 监控与告警:

关键指标:


  • 事务成功率

  • 事务执行时间

  • 补偿次数

  • 数据一致性检查


监控代码:


@Aspect@Componentpublic class TransactionMonitor {        @Around("@annotation(globalTransactional)")    public Object monitorTransaction(ProceedingJoinPoint pjp) throws Throwable {        long startTime = System.currentTimeMillis();        try {            Object result = pjp.proceed();            // 记录成功指标            recordSuccess(System.currentTimeMillis() - startTime);            return result;        } catch (Exception e) {            // 记录失败指标            recordFailure(e);            throw e;        }    }}
复制代码

3. 避坑指南:

❌ 不要这样做:


  • 在分片场景下使用传统两阶段提交

  • 忽略补偿逻辑的幂等性

  • 不监控事务执行状态

  • 事务边界设计过大


✅ 要这样做:


  • 根据业务场景选择合适的模式

  • 设计幂等的补偿逻辑

  • 建立完善的监控体系

  • 定期进行数据一致性检查



八、总结

在分片场景下实现分布式事务,TCC 和 Seata 各有优势。


记住这三点:


  1. TCC 适合高性能、复杂业务场景

  2. Seata 适合快速开发、简单业务场景

  3. 设计时要考虑补偿逻辑的幂等性


最后提醒:分布式事务是分片架构的"硬骨头",设计时一定要深思熟虑,宁可多花时间设计,也不要上线后再改!




关注服务端技术精选,获取更多后端实战干货!


你在分布式事务实现上踩过哪些坑?欢迎在评论区分享你的故事!

用户头像

个人博客: http://jiangyi.cool 2019-03-10 加入

公众号:服务端技术精选 欢迎大家关注!

评论

发布
暂无评论
分布式事务在分片场景下,TCC和Seata到底怎么选?一线实战全解析!_分布式事务_我爱娃哈哈😍_InfoQ写作社区