@Transactional导致数据库连接数不够

news/2024/9/27 18:40:37 标签: 数据库

   在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。甚至很多人心里已经将Spring事务@Transactional划上了等号,只要有数据库相关操作就直接给方法加上@Transactional注解。

 
  1. /**

  2. * 代码片段1

  3. */

  4. @Transactional(rollbackFor = Exception.class)

  5. public void save(RequestBillDTO requestBillDTO){

  6. //调用流程HTTP接口创建工作流

  7. workflowUtil.createFlow("BILL",requestBillDTO);

  8. //转换DTO对象

  9. RequestBill requestBill = JkMappingUtils.convert(requestBillDTO, RequestBill.class);

  10. requestBillDao.save(requestBill);

  11. //保存明细表

  12. requestDetailDao.save(requestBill.getDetail())

  13. }

        先通过http接口调用工作流引擎创建审批流,然后保存,而为了保证 操作的事务,在整个方法上加上了@Transactional注解(仔细想想,这样真的能保证事务吗? )。

        代码发布上线后,系统开始出现了故障:数据库监控平台一直收到告警短信,数据库连接不足,出现大量死锁;日志显示调用流程引擎接口出现大量超时;同时一直提示CannotGetJdbcConnectionException数据库连接池连接占满。在发生故障后,我们尝试过杀掉死锁进程,也进行过暴力重启,只是不到10分钟故障再次出现。

        我们知道@Transactional 注解,是使用 AOP 实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。

        当 Spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于@Transactional注解包裹的整个方法都是使用同一个connection连接 。如果我们出现了耗时的操作,比如第三方接口调用,业务逻辑复杂,大批量数据处理等就会导致我们我们占用这个connection的时间会很长,数据库连接一直被占用不释放。一旦类似操作过多,就会导致数据库连接池耗尽。

        在一个事务中执行RPC操作导致数据库连接池撑爆属于是典型的长事务问题 ,类似的操作还有在事务中进行大量数据查询,业务规则处理等...

何为长事务?

顾名思义就是运行时间比较长,长时间未提交的事务,也可以称之为大事务 。

长事务会引发哪些问题?

长事务引发的常见危害有:

  1. 数据库连接池被占满,应用无法获取连接资源;

  2. 容易引发数据库死锁;

  3. 数据库回滚时间长;

  4. 在主从架构中会导致主从延时变大。

如何避免长事务?

解决长事务的宗旨就是 对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度。

既然提到了事务的颗粒度,我们就先回顾一下Spring进行事务管理的方式。

声明式事务

首先我们要知道,通过在方法上使用@Transactional注解进行事务管理的操作叫声明式事务 。

使用声明式事务的优点 很明显,就是使用很简单,可以自动帮我们进行事务的开启、提交以及回滚等操作。使用这种方式,程序员只需要关注业务逻辑就可以了。

声明式事务有一个最大的缺点 ,就是事务的颗粒度是整个方法,无法进行精细化控制。

与声明式事务对应的就是编程式事务 。基于底层的API,开发者在代码中手动的管理事务的开启、提交、回滚等操作。在spring项目中可以使用TransactionTemplate类的对象,手动控制事务。

 
  1. private TransactionTemplate transactionTemplate;

  2. ...

  3. public void save(RequestBill requestBill) {

  4. transactionTemplate.execute(transactionStatus -> {

  5. requestBillDao.save(requestBill);

  6. //保存明细表

  7. requestDetailDao.save(requestBill.getDetail());

  8. return Boolean.TRUE;

  9. });

  10. }

使用编程式事务最大的好处就是可以精细化控制事务范围。

所以避免长事务最简单的方法就是不要使用声明式事务@Transactional,而是使用编程式事务手动控制事务范围。

有的同学会说,@Transactional使用这么简单,有没有办法既可以使用@Transactional,又能避免产生长事务?

那就需要对方法进行拆分,将不需要事务管理的逻辑与事务操作分开:

 
  1. @Service

  2. public class OrderService{

  3. public void createOrder(OrderCreateDTO createDTO){

  4. query();

  5. validate();

  6. saveData(createDTO);

  7. }

  8. //事务操作

  9. @Transactional(rollbackFor = Throwable.class)

  10. public void saveData(OrderCreateDTO createDTO){

  11. orderDao.insert(createDTO);

  12. }

  13. }

query()validate()不需要事务,我们将其与事务方法saveData()拆开。

当然,这种拆分会命中使用@Transactional注解时事务不生效的经典场景,很多新手非常容易犯这个错误。@Transactional注解的声明式事务是通过spring aop起作用的,而spring aop需要生成代理对象,直接在同一个类中方法调用使用的还是原始对象,事务不生效。其他几个常见的事务不生效的场景为:

  • @Transactional 应用在非 public 修饰的方法上

  • @Transactional 注解属性 propagation 设置错误

  • @Transactional 注解属性 rollbackFor 设置错误

  • 同一个类中方法调用,导致@Transactional失效

  • 异常被catch捕获导致@Transactional失效

正确的拆分方法应该使用下面两种:

  1. 可以将方法放入另一个类,如新增 manager层,通过spring注入,这样符合了在对象之间调用的条件。

     
      
    1. @Service

    2. public class OrderService{

    3. @Autowired

    4. private OrderManager orderManager;

    5. public void createOrder(OrderCreateDTO createDTO){

    6. query();

    7. validate();

    8. orderManager.saveData(createDTO);

    9. }

    10. }

    11. @Service

    12. public class OrderManager{

    13. @Autowired

    14. private OrderDao orderDao;

    15. @Transactional(rollbackFor = Throwable.class)

    16. public void saveData(OrderCreateDTO createDTO){

    17. orderDao.saveData(createDTO);

    18. }

    19. }

  2. 启动类添加@EnableAspectJAutoProxy(exposeProxy = true),方法内使用AopContext.currentProxy()获得代理类,使用事务。

     
      
    1. SpringBootApplication.java

    2. @EnableAspectJAutoProxy(exposeProxy = true)

    3. @SpringBootApplication

    4. public class SpringBootApplication {}

    5. OrderService.java

    6. public void createOrder(OrderCreateDTO createDTO){

    7. OrderService orderService = (OrderService)AopContext.currentProxy();

    8. orderService.saveData(createDTO);

    9. }

    小结

    使用@Transactional注解在开发时确实很方便,但是稍微不注意就可能出现长事务问题。所以对于复杂业务逻辑,我这里更建议你使用编程式事务来管理事务,当然,如果你非要使用@Transactional,可以根据上文提到的两种方案进行方法拆分。


http://www.niftyadmin.cn/n/5679274.html

相关文章

检查代码中的数据引用错误

1. 是否有引用的变量未赋值或未初始化?这可能是最常见的编程错误,在各种环境中都可能发生。在引用每个数据项(如变量、数组元素、结构中的域)时,应试图非正式地“证明”该数据项在当前位置具有确定的值。 2. 对于所有的…

基于单片机无线智能报警系统的设计

文章目录 前言资料获取设计介绍功能介绍设计程序具体实现截图设计获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对…

智能手机取证: 专家如何从被锁定设备中提取数据?

在数字取证领域,从被锁定的手机中检索数据的能力是决定调查成功与否的关键技能。由于智能手机往往是解决复杂案件的关键,智能手机取证已经成为打击犯罪和恐怖主义战争中的一个关键组成部分。通话记录、短信、电子邮件,甚至位置数据都可能被发…

19.2 编写dockerfile和k8s yaml

本节重点介绍 : 编写Dockerfile编写k8s需要的yaml 编写Dockerfile 1. FROM 指定基础镜像 必须有的指令,并且必须是第一条指令Alpine 操作系统是一个面向安全的轻型 Linux 发行版。它不同于通常 Linux 发行版,Alpine 采用了 musl libc 和 busybox 以减…

(附代码)psutil实时监控脚本运行过程中消耗的资源

在运行脚本时有时需要监控脚本中各模块 占用的cpu以及memory的情况,一般是执行python xx.py后,另起temernal,输入top命令实时监控,但这个存在一个问题,当脚本运行时间比较久时,一直盯着屏幕 也不合适。 所…

LCR 007. 三数之和

文章目录 1.题目2.思路3.代码 1.题目 LCR 007. 三数之和 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c *,*使得 a b c 0 ?请找出所有和为 0 且 不重复 的三元组。 示例 1&#xff1a…

【达梦数据库】存储过程统计模式下表信息-SQL改写

背景 在一次Oracle迁移Dm的过程中,源库&目的库大小写均敏感,执行客户提供的SQL脚本的过程中发现,表ip_address被系统默认成了表IP_ADDRESS。 经过分析,客户提供的SQL没有使用双引号,来确保Oracle和Dm数据库按照指…

【多样化的思想】基于执行档案的测试

下面我们讨论另一种关于多样性的观点。我们知道,对被测对象而言,测试输入空间代表的是各种可能的外部环境条件。如果两个测试输入点距离比较远,说明在这两个点上,被测对象所面对的外部环境条件很不一样,所以我们说&…