Spring 事务与非事务状态获取释放连接的区别

警告
本文最后更新于 2021-03-14,文中内容可能已过时。

spring声明式事务使用时如果不注意,很容易造成连接用尽而导致线程阻塞。

1 事务流程

spring声明式事务是动态代理实现的,在spring声明式事务中,完成一个事务需经历以下步骤:

  1. 获取数据库连接、关闭自动提交,将事务信息封装起来缓存,在这个缓存中记录前一个事务信息。
  2. 执行业务方法。
  3. 提交或回滚事务。
  4. 释放连接。

而执行业务方法同样可以分为几个步骤,这里和orm框架集成spring的机制有关,以mybatis为例:

  1. 事务切面前置增强执行完毕,进入业务方法。
  2. 业务方法中有访问数据库,则使用 TransactionSynchronizationManager 中与线程绑定的连接,进行操作。
  3. 如果有多条sql语句,则会使用上述连接,也就是说,这多条sql使用的同一个连接。(非事务方式是直接从连接池获取连接)
  4. 如果这多条sql之间夹杂一些业务代码,那么,执行这些业务代码时,同样会保持对这个连接的占用。
  5. 直到业务方法执行完毕,回到事务切面的后置通知,提交或回滚事务后,才会释放连接。

2 非事务流程

如果一个业务方法没有开启声明式事务,则不会走事务切面。

以 mybatis-spring 为例,如果业务方法中,有访问数据库的操作。则每次访问数据库后,都会自动提交并释放连接。

非事务方式的业务方法,对数据库连接的释放是非常及时的。在使用连接的过程中不会夹杂别的业务代码。

3 引发的问题

使用声明式事务,如果编码不当,会导致延迟释放连接。连接资源是宝贵的,延迟连接的释放,直接影响到系统的吞吐量。(总有一些操作是不走缓存需要入库的。)

4 解决方式

  1. 减小事务的粒度。一个接口对应的业务方法,并不是所有操作都需要在一个事务中执行的,把这些不需要加入事务的执行逻辑与事务方法隔离开。

    比如:接口参数的规则校验,部分需要链接数据库的参数校验(仅是部分,有一些还是需要在事务中以类似版本号的方式判断的)。

  2. 不要把跨进程调用的代码放到一个事务中,网络的交互是不稳定且耗时的。如果需要远程调用,又需要保证一致性,可以使用补偿(通知)机制。

总的来说,就是尽可能把事务方法中的执行时长压缩到最短,这样就可以尽早释放持有的数据库连接,以供其他线程使用。