@Transactional导致数据库连接数不够
在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。甚至很多人心里已经将Spring事务@Transactional划上了等号,只要有数据库相关操作就直接给方法加上@Transactional注解。
-
/** -
* 代码片段1 -
*/ -
@Transactional(rollbackFor = Exception.class) -
public void save(RequestBillDTO requestBillDTO){ -
//调用流程HTTP接口创建工作流 -
workflowUtil.createFlow("BILL",requestBillDTO); -
//转换DTO对象 -
RequestBill requestBill = JkMappingUtils.convert(requestBillDTO, RequestBill.class); -
requestBillDao.save(requestBill); -
//保存明细表 -
requestDetailDao.save(requestBill.getDetail()) -
}
先通过http接口调用工作流引擎创建审批流,然后保存,而为了保证 操作的事务,在整个方法上加上了@Transactional注解(仔细想想,这样真的能保证事务吗? )。
代码发布上线后,系统开始出现了故障:数据库监控平台一直收到告警短信,数据库连接不足,出现大量死锁;日志显示调用流程引擎接口出现大量超时;同时一直提示CannotGetJdbcConnectionException,数据库连接池连接占满。在发生故障后,我们尝试过杀掉死锁进程,也进行过暴力重启,只是不到10分钟故障再次出现。
我们知道@Transactional 注解,是使用 AOP 实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
当 Spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于@Transactional注解包裹的整个方法都是使用同一个connection连接 。如果我们出现了耗时的操作,比如第三方接口调用,业务逻辑复杂,大批量数据处理等就会导致我们我们占用这个connection的时间会很长,数据库连接一直被占用不释放。一旦类似操作过多,就会导致数据库连接池耗尽。
在一个事务中执行RPC操作导致数据库连接池撑爆属于是典型的长事务问题 ,类似的操作还有在事务中进行大量数据查询,业务规则处理等...
何为长事务?
顾名思义就是运行时间比较长,长时间未提交的事务,也可以称之为大事务 。
长事务会引发哪些问题?
长事务引发的常见危害有:
-
数据库连接池被占满,应用无法获取连接资源;
-
容易引发数据库死锁;
-
数据库回滚时间长;
-
在主从架构中会导致主从延时变大。
如何避免长事务?
解决长事务的宗旨就是 对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度。
既然提到了事务的颗粒度,我们就先回顾一下Spring进行事务管理的方式。
声明式事务
首先我们要知道,通过在方法上使用@Transactional注解进行事务管理的操作叫声明式事务 。
使用声明式事务的优点 很明显,就是使用很简单,可以自动帮我们进行事务的开启、提交以及回滚等操作。使用这种方式,程序员只需要关注业务逻辑就可以了。
声明式事务有一个最大的缺点 ,就是事务的颗粒度是整个方法,无法进行精细化控制。
与声明式事务对应的就是编程式事务 。基于底层的API,开发者在代码中手动的管理事务的开启、提交、回滚等操作。在spring项目中可以使用TransactionTemplate类的对象,手动控制事务。
-
private TransactionTemplate transactionTemplate; -
... -
public void save(RequestBill requestBill) { -
transactionTemplate.execute(transactionStatus -> { -
requestBillDao.save(requestBill); -
//保存明细表 -
requestDetailDao.save(requestBill.getDetail()); -
return Boolean.TRUE; -
}); -
}
使用编程式事务最大的好处就是可以精细化控制事务范围。
所以避免长事务最简单的方法就是不要使用声明式事务@Transactional,而是使用编程式事务手动控制事务范围。
有的同学会说,@Transactional使用这么简单,有没有办法既可以使用@Transactional,又能避免产生长事务?
那就需要对方法进行拆分,将不需要事务管理的逻辑与事务操作分开:
-
@Service -
public class OrderService{ -
public void createOrder(OrderCreateDTO createDTO){ -
query(); -
validate(); -
saveData(createDTO); -
} -
//事务操作 -
@Transactional(rollbackFor = Throwable.class) -
public void saveData(OrderCreateDTO createDTO){ -
orderDao.insert(createDTO); -
} -
}
query()与validate()不需要事务,我们将其与事务方法saveData()拆开。
当然,这种拆分会命中使用@Transactional注解时事务不生效的经典场景,很多新手非常容易犯这个错误。@Transactional注解的声明式事务是通过spring aop起作用的,而spring aop需要生成代理对象,直接在同一个类中方法调用使用的还是原始对象,事务不生效。其他几个常见的事务不生效的场景为:
“
@Transactional 应用在非 public 修饰的方法上
@Transactional 注解属性 propagation 设置错误
@Transactional 注解属性 rollbackFor 设置错误
同一个类中方法调用,导致@Transactional失效
异常被catch捕获导致@Transactional失效
”
正确的拆分方法应该使用下面两种:
-
可以将方法放入另一个类,如新增
manager层,通过spring注入,这样符合了在对象之间调用的条件。-
@Service -
public class OrderService{ -
@Autowired -
private OrderManager orderManager; -
public void createOrder(OrderCreateDTO createDTO){ -
query(); -
validate(); -
orderManager.saveData(createDTO); -
} -
} -
@Service -
public class OrderManager{ -
@Autowired -
private OrderDao orderDao; -
@Transactional(rollbackFor = Throwable.class) -
public void saveData(OrderCreateDTO createDTO){ -
orderDao.saveData(createDTO); -
} -
}
-
-
启动类添加
@EnableAspectJAutoProxy(exposeProxy = true),方法内使用AopContext.currentProxy()获得代理类,使用事务。-
SpringBootApplication.java -
@EnableAspectJAutoProxy(exposeProxy = true) -
@SpringBootApplication -
public class SpringBootApplication {} -
OrderService.java -
public void createOrder(OrderCreateDTO createDTO){ -
OrderService orderService = (OrderService)AopContext.currentProxy(); -
orderService.saveData(createDTO); -
}
小结
使用
@Transactional注解在开发时确实很方便,但是稍微不注意就可能出现长事务问题。所以对于复杂业务逻辑,我这里更建议你使用编程式事务来管理事务,当然,如果你非要使用@Transactional,可以根据上文提到的两种方案进行方法拆分。 -
相关文章:
@Transactional导致数据库连接数不够
在Spring中进行事务管理非常简单,只需要在方法上加上注解Transactional,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。甚至很多人心里已经将Spring事务Transactional划上了等号,只要有数据库相关操作就直接给方法加上Transactiona…...
python3中的string 和bytes有什么区别
在Python中,string(字符串)和bytes(字节序列)是两种不同的数据类型,分别用于表示文本和二进制数据。它们的主要区别在于存储的数据类型、编码方式以及使用场景。 1. 存储数据类型 string (字符串,str):用来表示文本数据。string是一个Unicode字符串,其中的每个字符是…...
C~排序算法
在C/C中,有多种排序算法可供选择,每种算法都有其特定的应用场景和特点。下面介绍几种常用的排序算法,包括冒泡排序、选择排序、插入排序、快速排序、归并排序和堆排序,并给出相应的示例代码和解释。 冒泡排序(Bubble …...
基于github创建个人主页
基于github创建个人主页 站在巨人的肩膀上,首先选一个创建主页的仓库进行fork,具体可以参照这篇文章https://blog.csdn.net/qd1813100174/article/details/128604858主要总结下需要修改的地方: 1)仓库名字要和github的名字一致&a…...
apt update时出现证书相关问题,可以关闭apt验证
vi /etc/apt/apt.conf.d/99disable-signature-verification 添加以下内容: Acquire::AllowInsecureRepositories "true"; Acquire::AllowDowngradeToInsecureRepositories "true"; Acquire::AllowUnauthenticated "true"; 参考链…...
进阶数据库系列(十三):PostgreSQL 分区分表
概述 在组件开发迭代的过程中,随着使用时间的增加,数据库中的数据量也不断增加,因此数据库查询越来越慢。 通常加速数据库的方法很多,如添加特定的索引,将日志目录换到单独的磁盘分区,调整数据库引擎的参…...
翻译:Recent Event Camera Innovations: A Survey
摘要 基于事件的视觉受到人类视觉系统的启发,提供了变革性的功能,例如低延迟、高动态范围和降低功耗。本文对事件相机进行了全面的调查,并追溯了事件相机的发展历程。它介绍了事件相机的基本原理,将其与传统的帧相机进行了比较&am…...
车载诊断技术:汽车健康的守护者
一、车载诊断技术的发展历程 从最初简单的硬件设备到如今智能化、网络化的系统,车载诊断技术不断演进,为汽车安全和性能提供保障。 早期的汽车诊断检测技术处于比较原始的状态,主要依靠操作经验和主观评价。随着汽车工业的发展,车载诊断技术也经历了不同的阶段。20 世纪初…...
“天翼云息壤杯”高校AI大赛开启:国云的一场“造林”计划
文 | 智能相对论 作者 | 叶远风 2024年年初《政府工作报告》中明确提到了“人工智能”行动,人工智能的发展被提到前所未有的高度。 如何落实AI在数字经济发展中引擎作用,是业界当下面临的课题。 9月25日,“2024年中国国际信息通信展览会”…...
【怎样基于Okhttp3来实现各种各样的远程调用,表单、JSON、文件、文件流等待】
HTTP客户端工具 okhttp3 form/json/multipart 提供表达、json、混合表单、混合表单文件流传输等HTTP请求调用支持自定义配置默认客户端,参数列表如下: okhtt3.config.connectTimeout 连接超时,TimeUnit.SECONDSokhtt3.config.readTimeOut 读…...
excel统计分析(3): 一元线性回归分析
简介 用途:研究两个具有线性关系的变量之间的关系。 一元线性回归分析模型: ab参数由公式可得: 判定系数R2:评估回归模型的拟合效果。值越接近1,说明拟合效果越好;值越接近0,说明拟合效果越…...
搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(一)-概述
简介 此前的专栏介绍onesearch1.0和2.0,详情参看4 参考资料,本文解释onesearch 3.0,从Elasticsearch6升级到Elasticsearch8代码实现 ,Elasticsearch8 废弃了high rest client,使用新的ElasticsearchClient,…...
ArcGIS Pro高级地图可视化—双变量符号地图
ArcGIS Pro高级地图可视化 ——双变量符号地图 1 背景 “我不是双变量,但我很好奇。”出自2013 年南卡罗来纳州格林维尔举行的 NACIS 会议上,双变量地图随着这句俏皮的话便跳跃在人们的视角下,在讨论二元映射之后,它不仅恰逢其…...
rust属性宏
1. #[repr(xxx)] repr全称是 “representation”,即表示、展现的意思。在#[repr(u32)]中,u32表示无符号 32 位整数。这意味着被这个属性修饰的类型将以 32 位无符号整数的形式在内存中存储和布局。例如,如果有一个枚举类型被#[repr(u32)]修饰: #[repr(u32)] enum MyEnum {…...
《pyqt+open3d》open3d可视化界面集成到qt中
《pyqtopen3d》open3d可视化界面集成到qt中 一、效果显示二、代码三、资源下载 一、效果显示 二、代码 参考链接 main.py import sys import open3d as o3d from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget from PyQt5.QtGui import QWindow from PyQt5.Qt…...
学习记录:js算法(四十七):相同的树
文章目录 相同的树我的思路网上思路队列序列化方法 总结 相同的树 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 图一: 图二&…...
使用Hutool-poi封装Apache POI进行Excel的上传与下载
介绍 Hutool-poi是针对Apache POI的封装,因此需要用户自行引入POI库,Hutool默认不引入。到目前为止,Hutool-poi支持: Excel文件(xls, xlsx)的读取(ExcelReader)Excel文件(xls&…...
asp.net core grpc快速入门
环境 .net 8 vs2022 创建 gRPC 服务器 一定要勾选Https 安装Nuget包 <PackageReference Include"Google.Protobuf" Version"3.28.2" /> <PackageReference Include"Grpc.AspNetCore" Version"2.66.0" /> <PackageR…...
拿到一个新项目,如何开展测试
1. 拿到一个新的项目或者新的需求,首先需要搞清楚他的背景、目标和需求,这个过程需要和产品、开发、客户去沟通。 2. 清楚需求后,首先将业务流程走通,确保项目的基础功能是正常的 3. 根据项目需求明确测试的目标,如&…...
pre-commit 的配置文件
这个文件是 pre-commit 的配置文件,通常命名为 .pre-commit-config.yaml。pre-commit 是一个用于管理和维护多种预提交钩子的框架,旨在在代码提交(git commit)之前自动执行一系列检查和格式化任务,以确保代码质量和一致…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...
