springboot项目使用@Transactional注解如何避免长事务问题
- 发表于:2022-03-10 11:43
- 阅读(2743)
(1)如何使用注解开启事务
我们平时想要在某个方法上开启事务的话,一般都会直接在方法上加一个注解:@Transactional
,例如:
@Transactional(rollbackFor = Exception.class)
public Boolean handleSomething() {
// 获取信息 ==> select
// 更新信息 ==> update
// 调用生成记录方法 ==> insert
}
在程序执行的时候,一般会是这样:
执行获取信息 select 的时候,会给当前获取的数据库表记录添加一个 Shared Lock 共享锁,共享锁是允许多个 session 同时进入;
执行更新信息 update 的时候,会给当前获取的数据库表记录添加一个 Exclusive Lock 独占锁;
(2)如何避免数据库死锁问题
1、死锁的产生原因:
当有两个 session 并发执行同一条数据库记录的时候,这两个 session 执行 select 都获得了 Shared Lock ,而接下来执行 update 时都在获取 Exclusive Lock 的同时等待对方释放 Shared Lock ,从而导致了死锁。Mysql监测到死锁时,会回滚掉undo日志量最小的那个session的事务(持有最少行级排它锁的事务回滚)
注意:共享锁 Shared Lock ,是读取操作创建的锁。所有请求可以并发读取数据,即给该优惠券row行上加了多把共享锁,此时任何事务都不能对该优惠券进行修改(就是获取该优惠券的排他锁),直到已释放所有的共享锁。因为MySQL默认事务隔离级别是可重复读(REPEATABLE READ),事务A读取到的优惠券信息,在事务A还未提交事务时,事务B不能把这个优惠券信息给改了。
2、如何解决或者避免死锁问题:
- 尽量避免在同一个事务中先 select 再 update (这样会导致锁定同一条数据库表记录),除非保证当前接口是串行调用的,而不能是并行调用;
- mysql 单节点时,可以使用 select … for update 对操作的记录加个悲观锁。但是在集群下,select … for update 锁住的是每个节点的 row,之后进行 update 无法保证死锁;
- 确实需要这么做的话,需要在 select 之前先做一个不产生任何修改的 update ,先直接抢到 Exclusive Lock ,然后再执行后续操作;
注意:如果是以下这种 sql 语句,在事务中,高并发调用的时候,也会造成死锁:
update demo set a = a+1, b = b-1, update_time = now() where id = 1;
这种在 sql 语句中直接用字段当前的值进行运算的情况也有可能会造成死锁,因为 a = a+1,b = b-1,update_time = now() ,要先读取字段a、b、now()的值,这种写法会先去获取 id=1 的 row 的 Shared Lock 共享锁再去拿 Exclusive Lock 独占锁。
(3)如何避免长事务问题
1、长事务问题产生的原因:
一般是在一个方法中,例如上面的方法,在这个方法上标记了 @Transactional 注解,然后在这个方法里面又写了通过RPC框架对第三方系统接口的调用,或者在里面大量的查询和修改都夹杂在一起,业务逻辑复杂,这种大批量数据处理就会导致这个长事务占用的数据库连接 connection 的时间会很长,这个 connection 一直被占用不被释放,一旦高并发,有多个这样的 connection ,就容易导致数据库连接池占满耗尽。
在一个事务中执行RPC操作导致数据库连接池撑爆属于是典型的长事务问题,类似的操作还有在事务中进行大量数据查询,业务规则处理等。
2、什么是长事务:
运行时间比较长,长时间未提交的事务,也可以称之为长事务。
长事务会导致这些问题:
数据库连接池被占满,应用无法获取连接资源;
容易引发数据库死锁;
数据库回滚时间长;
在主从架构中会导致主从延时变大。
3、如何避免长事务:
解决长事务的宗旨就是 对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度。
spring支持 编程式事务管理 和 声明式事务管理(也就是在方法上使用 @Transactional 注解进行事务管理) 两种方式。
声明式事务有一个最大的缺点,就是事务的颗粒度是整个方法,无法进行精细化控制。如果非要用 @Transactional 注解,就需要把必要的操作都抽出出来单独一个子方法加 @Transactional 注解,在主方法不需要加事务注解,因为查询操作不需要用到事务,如下:
// 这里没必要开启事务,没必要加事务注解 @Transactional(rollbackFor = Throwable.class)
public void doSomething(Demo demo){
// 查询信息,不需要开启事务
query();
// 校验信息,不需要开启事务
validate();
// 操作信息,注意:由于是在同一个类中调用,我们需要通过getThis()获取当前类的代理对象,通过代理对象调用save方法,getThis()应该如何写请自行百度,或者把save方法放到另外单独的一个类中去调用。
getThis().save(createDTO);
}
// 这里单独开启事务即可
@Transactional(rollbackFor = Throwable.class)
public void save(Demo demo){
// 保存数据
demoDao.insert(demo);
// 其他操作 ...
}
与声明式事务对应的就是编程式事务,我们其实可以使用编程式事务,来灵活控制那些语句需要开启事务管理。
如何开启编程式事务呢?可以参考如下写法:
// 注入 TransactionTemplate 事务管理实例
// 基于底层的API,开发者在代码中手动的管理事务的开启、提交、回滚等操作。在spring项目中可以使用TransactionTemplate类的对象,手动控制事务。
@Autowired
private TransactionTemplate transactionTemplate;
public void doSomething(Demo demo) {
// 查询信息
// 调用第三方接口
// 对这块操作手动开启事务,使用编程式事务最大的好处就是可以精细化控制事务范围。
transactionTemplate.execute(transactionStatus -> {
// 保存信息
demoDao.save(demo);
// 保存详情信息
demoDetailDao.save(demo.getDetail());
// 其他操作 ...
// 返回结果对象或 true 即可
return Boolean.TRUE;
});
}
避免长事务最简单的方法就是不要使用声明式事务@Transactional,而是使用编程式事务手动控制事务范围。
4、编程式事务和声明式事务的对比:
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于 @Transactional 注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。
和编程式事务相比,声明式事务唯一不足地方是最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
参考:
TransactionTemplate和@Transactional注解的区别
我想最小化@Transactional的范围吗?
Spring事务管理的另一种方式—TransactionTemplate编程式事务管理简单入门
大意了,使用@Transactional竟然出现了长事务,导致生产事故!
事物导致的死锁问题分析
使用 @Transactional 时常犯的N种错误
相关推荐
-
mac安装subversion,并使用svn命令检出服务器上的代码库项目
mac安装svn只要通过Homebrew安装即可,不需要下载额外的安装包手动安装,Homebrew类似一个软件库,我们可以通过brew命令实现一键下载并安装我们所需要的常用软件。
-
Java如何获取泛型类T的Class
我们平时在封装接口或抽象类的时候经常会用到Java的泛型,经常会在传入一个泛型类T,然后封装一些抽象的方法,泛型的好处就是在编译的时候检查类型安全,并且所有的强制类型转换都是隐式和自动的,这样可以提高代码的通用性。但是我们有时候需要获取泛型类的Class,那可以如何获取到呢?
-
springboot项目事务报错:Transaction synchronization is not active
这几天在使用spring声明式事务的时候突然报了一个错误:Transaction synchronization is not active,之前使用的都是好好的,为什么这次就不行了呢?不就是加一个 @Transactional 而已嘛???
-
mysql应该如何在where语句中添加if语句进行条件判断?where if 语句应该如何使用
我们在平时的项目开发中,有时候会遇到复杂一点的需求,需要我们手动编写复杂的SQL语句,并且有时候需要根据每条表记录的实际情况进行判断,根据每条记录动态添加不同的where条件,这个时候我们就可以在where语句中使用if语句进行条件判断,那么where if应该如何正确使用呢?
-
关于websocket多节点分布式问题的解决方案
websocket是一种在单个TCP连接上进行双全工通信的协议,使用websocket,我们可以实现服务端主动向各个订阅消息通道的客户端推送消息。这点比传统的http轮询请求要更好一点,避免一些无用的请求,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
-
mac如何把文件压缩成tar、zip以及如何解压tar、zip?
有时候我们需要把文件压缩成一个tar文件或zip文件,发送给别人,那么在macos系统应该如何压缩和解压缩呢?
-
openjdk和jdk有什么区别,应该如何选择?
我们一开始学习java的时候,安装的都是从sun官网或oracle官网下载的jdk安装包,但其实还有另外一个来源可以获取到jdk安装包,那就是openjdk,它和jdk基本一样,推荐使用openjdk。
-
如果git仓库发生变更,IDEA如何直接修改git远程仓库地址?
有时候我们整理远程仓库代码的时候,会修改远程仓库的名称,或者所属分组,这个时候在IDEA由于还是使用原先拉取的旧仓库地址,导致本地代码会提交不了,也更新不了远程最新代码,那么这个时候要如何修改IDEA当前的git远程仓库地址呢?如何无缝修改,修改完之后就能和原来一样更新提交,并且以前的提交记录也保留呢?
-
微信公众号自定义菜单报错:no permission to use weapp in menu rid:xxxxxxx
昨晚公司系统添加微信公众号菜单突然报错:{"errcode":45064,"errmsg":"no permission to use weapp in menu rid: 60311f70-0736ff08-29143906"}
-
Java如何使用stream流对List列表数据进行自定义排序
我们一般做排序功能都是通过在mysql数据库中的表中定义好排序字段,然后使用升序或降序来进行排序,复杂一点的话就配合多个排序字段进行排序,但是如果碰到那种无法使用表的字段进行排序的情况,我们需要先从数据库中取出列表数据,然后再通过业务代码对列表进行排序,这个时候我们就可以使用redis或Java的stream流。
-
微信企业付款到零钱报错:此请求可能存在风险,已被微信拦截
具体错误信息:com.github.binarywang.wxpay.exception.WxPayException: 返回代码:[SUCCESS],返回信息:[支付失败],结果代码:[FAIL],错误代码:[NO_AUTH],错误详情:[此请求可能存在风险,已被微信拦截。]
-
springboot项目使用@Transactional注解如何避免长事务问题
在springboot项目中,我们开启事务是非常简单的,使用注解的方式就是在需要开启事务的方法上添加@Transactional,这样就可以实现这个方法里面的所有操作和调用方法的操作都绑定在一个事务上面,要么全部一起执行成功,要么全部一起执行失败,如果其中有某个地方抛了异常,则整个方法涉及的事务操作都会回滚,但是如果随意滥用@Transactional,又有可能引发长事务问题,导致数据库死锁、数据库连接池占满等问题。
-
css实现“展开阅读全文”功能
最近发现很多博客网站,资讯网站喜欢把资讯博文,内容等这些大文本的信息在页面显示的时候都会有个“展开阅读全文”的按钮,点击这个按钮即可展开显示所有的内容,不然一开始就显示那么长的篇幅相对来说既不美观,又对用户体验不好。现在就让我来仿照这类网站实现一个“展开阅读全文”功能。这里主要用到的前端技术是html+jquery+css,只做展开功能,没做收起功能(收起功能没必要吧,谁会去收起呀???)。
-
关于编程中面向对象的理解,什么是面向对象
面向对象设计相对于结构化程序设计可以说是一种更优秀的程序设计方法。它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。它是从现实世界中客观存在的事物(即对象)出发来构造软件系统,并在软件系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考,认识问题。
-
我的linux操作命令总结,记录常用linux操作命令
平常本地开发项目使用的系统基本都是window系统,而且都是图形化操作,非常方便,window也是越做越好了,项目部署到生产环境一般都是选择linux系统(当然window server系列也可以),而linux一般则选择centOS居多,这里记录一下linux常用命令,以免老是过几天就忘了,后续不断补充。