面对接口中的大事务,你需要考虑这些

什么是大事务

在日常的开发过程中,后端人员需要根据业务需求给前端的小伙伴提供一个可调用的服务接口,在这个接口中可能会涉及数据查询、远程服务调用、组装数据计算以及最终落库等操作,为了保证这些操作数据的可靠性就需要使用到事务。随着数据量的增长以及其它远程服务的调用时间长等情况,接口中需要执行的SQL比较多,导致最终事务的执行时间拉长,这就是所谓的大事务。

大事务的隐患

  • 1、占用数据库连接
  • 2、锁定数据增多,造成大量的锁阻塞和响应超时
  • 3、执行时间过长,增加主从的延迟
  • 4、比较难回滚
  • 5、占用日志空间

优化方案

那么针对上面的这些隐患,作为优秀的后端开发人员该如何进行优化处理呢?

使用编程式事务

为了提高开发效率,我们常常习惯于在业务方法上添加声明式事务@Transactional注解来进行事务处理。不过声明式事务不够灵活,加上了就意味着整个业务方法对于数据库的操作都要加入到事务中,包括查询操作。我们只需要将update、insert加入事务管理,通过使用编程式事务灵活的控制事务的范围,方便后续的数据回滚。

csharp 复制代码
  transactionTemplate.executeWithoutResult(transactionStatus -> {
   try {
      // 生成订单
      // 生成订单明细
      // 扣库存
      // 核销优惠券
      // 扣积分
      // 生成订单日志
      } catch(Exception e) {
        log.error("订单创建异常, error: ", e);
        transactionStatus.setRollbackOnly();
      }
    }
  )

查询放到事务外侧

将查询的方法放到服务的外侧,也是能够有效解决大事务的一种方案,因为查询方法不需要使用到事务,那么只需要将其放到事务外即可,例如:

csharp 复制代码
public void saveInfo (Params params, DTO dto) {
    query1(params);
    query2(params);
    transactionTemplate.execute(status -> {
      saveInfo(dto);
      return Boolean.TRUE;
    })
}

如果你继续想用声明式事务,那么可以通过下面的方式实现:

scss 复制代码
@Service
public class ServiceA {

  @Autowired
  private ServiceB serviceB;
  
  public void saveInfo(Params params, DTO dto) {
    query1(params);
    query2(params);
    serviceB.save(dto);
  }
}

@Service
public class ServiceB {
  
  @Transactional(rollbackFor = Exception.class)
  public void save(DTO dto) {
    save(dto);
  }
}

❝

注: 这里需要注意的是,我并没有在ServiceA中创建save方法,原因也很简单,就是为了避免事务失效的问题。声明式事务@Transactional注解是通过spring aop起作用的,需要生成代理对象,而如果通过方法调用ServiceA中的save方法,使用的还是原始对象,事务是不会生效的。比较简单的做法就是新建一个ServiceB,并且将声明式事务注解加到ServiceB的save方法上。如果不想新建一个Service,那么你也可以通过获取当前对象的代理对象(AopContext.currentProxy())去调用,也可以在ServiceA中注入ServiceA,再通过调用save方法也是可以的。具体可以去了解一下有关@Transactional事务失效的几个场景。

❞

事务中避免远程调用

在业务处理的接口中远程调用其它服务是比较正常的事情,由于受到网络波动的原因,远程调用的响应时间时间会比较长,也比较容易造成大事务的发生。针对这种情况,需要将远程调用放到事务外侧,通过自身重试以及兜底补偿等措施,实现最终一致性。

避免一次性处理过多数据

在数据量非常多的情况下,如果事务一次性处理的数据很多,会拉长事务的执行时间,通过分批处理的方式可以有效的解决该问题。例如有一批10000条数据需要更新,如果直接执行更新操作,会导致大量的数据锁等待,非常影响系统的正常业务处理。解决办法就是分批次处理,例如一次处理500条,分20次就能够处理完成(根据实际情况选择一次能处理多少数据)。这样做不仅能够减少大事务情况的出现,而且也不影响其它业务的正常处理。

异步

并不是所有的操作都需要同步进行,在业务允许的情况下,可以通过异步调用的方式进行处理,减少等待时间提升系统的吞吐量。使用异步处理,需要考虑的就是监控重试+兜底补偿,这一点必不可少。

总结

当你在面对大事务的问题时,可以采取上述的几种方案来解决。核心思想就是拆,既然是大事务,那么将他们拆成多个事务进行处理,不需要加入到事务中的操作就提出来,放到事务外侧。

如果这篇文章对您有帮助,帮忙点个免费的关注,求点赞、转发、在看,非常感谢! 今天的文章就写到这里了,我们下篇再会~~

相关推荐
Gust of wind7 小时前
串与KMP模式匹配:存储结构、基本操作、next数组手算
c语言·数据结构·后端·算法
endswel7 小时前
Spring bean 注册多种方式
java·后端·spring
铁皮饭盒7 小时前
还是网页端, 46mb模型, 抠图功能升级了, 抠任意主体, 还是不要显卡, 不要python, 满意吗?
前端·javascript·后端
小林coding7 小时前
Flash模型又添一员大将:实测MiniMax M3.1
后端
颜进强8 小时前
25 · NestJs DurableProviders 持久化 Provider:把 ContextId 当缓存键
前端·后端·ai编程
tltwuyulw8 小时前
Java的函数式编程(四)
后端
我的xiaodoujiao8 小时前
Django 基础知识详细图文教程 14-Django 表单定义与使用
开发语言·后端·python·django
颜进强8 小时前
24 · NestJs LazyLoadingModules 懒加载模块:你在前端天天 `import()` 懒路由,但 Nest 偏偏不能懒加载路由
前端·后端·ai编程
Mav8 小时前
[个人学习记录]从零构建高性能 LLM 推理网关:为什么普通轮询会击穿显存?Nginx 平滑加权轮询(SWRR)在 Go 中的 57ns 零分配实战
后端