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

什么是大事务

在日常的开发过程中,后端人员需要根据业务需求给前端的小伙伴提供一个可调用的服务接口,在这个接口中可能会涉及数据查询、远程服务调用、组装数据计算以及最终落库等操作,为了保证这些操作数据的可靠性就需要使用到事务。随着数据量的增长以及其它远程服务的调用时间长等情况,接口中需要执行的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次就能够处理完成(根据实际情况选择一次能处理多少数据)。这样做不仅能够减少大事务情况的出现,而且也不影响其它业务的正常处理。

异步

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

总结

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

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

相关推荐
EterNity_TiMe_23 分钟前
【Linux基础IO】深入Linux文件描述符与重定向:解锁高效IO操作的秘密
linux·运维·服务器·学习·性能优化·学习方法
程序员大金1 小时前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
程序员大金2 小时前
基于SpringBoot的旅游管理系统
java·vue.js·spring boot·后端·mysql·spring·旅游
Pandaconda2 小时前
【计算机网络 - 基础问题】每日 3 题(十)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
程序员大金3 小时前
基于SpringBoot+Vue+MySQL的养老院管理系统
java·vue.js·spring boot·vscode·后端·mysql·vim
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS网上购物商城(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
Ylucius3 小时前
JavaScript 与 Java 的继承有何区别?-----原型继承,单继承有何联系?
java·开发语言·前端·javascript·后端·学习
ღ᭄ꦿ࿐Never say never꧂4 小时前
微服务架构中的负载均衡与服务注册中心(Nacos)
java·spring boot·后端·spring cloud·微服务·架构·负载均衡
.生产的驴4 小时前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq