Spring 事务与非事务状态获取释放连接的区别
目录
警告
本文最后更新于 2021-03-14,文中内容可能已过时。
spring声明式事务使用时如果不注意,很容易造成连接用尽而导致线程阻塞。
1 事务流程
spring声明式事务是动态代理实现的,在spring声明式事务中,完成一个事务需经历以下步骤:
- 获取数据库连接、关闭自动提交,将事务信息封装起来缓存,在这个缓存中记录前一个事务信息。
- 执行业务方法。
- 提交或回滚事务。
- 释放连接。
而执行业务方法同样可以分为几个步骤,这里和orm框架集成spring的机制有关,以mybatis为例:
- 事务切面前置增强执行完毕,进入业务方法。
- 业务方法中有访问数据库,则使用 TransactionSynchronizationManager 中与线程绑定的连接,进行操作。
- 如果有多条sql语句,则会使用上述连接,也就是说,这多条sql使用的同一个连接。(非事务方式是直接从连接池获取连接)
- 如果这多条sql之间夹杂一些业务代码,那么,执行这些业务代码时,同样会保持对这个连接的占用。
- 直到业务方法执行完毕,回到事务切面的后置通知,提交或回滚事务后,才会释放连接。
2 非事务流程
如果一个业务方法没有开启声明式事务,则不会走事务切面。
以 mybatis-spring 为例,如果业务方法中,有访问数据库的操作。则每次访问数据库后,都会自动提交并释放连接。
非事务方式的业务方法,对数据库连接的释放是非常及时的。在使用连接的过程中不会夹杂别的业务代码。
3 引发的问题
使用声明式事务,如果编码不当,会导致延迟释放连接。连接资源是宝贵的,延迟连接的释放,直接影响到系统的吞吐量。(总有一些操作是不走缓存需要入库的。)
4 解决方式
-
减小事务的粒度。一个接口对应的业务方法,并不是所有操作都需要在一个事务中执行的,把这些不需要加入事务的执行逻辑与事务方法隔离开。
比如:接口参数的规则校验,部分需要链接数据库的参数校验(仅是部分,有一些还是需要在事务中以类似版本号的方式判断的)。
-
不要把跨进程调用的代码放到一个事务中,网络的交互是不稳定且耗时的。如果需要远程调用,又需要保证一致性,可以使用补偿(通知)机制。
总的来说,就是尽可能把事务方法中的执行时长压缩到最短,这样就可以尽早释放持有的数据库连接,以供其他线程使用。