REQUIRES_NEW导致数据库连接死锁

在项目中,我们使用Spring事务传播类型REQUIRES_NEW实现了子事务的独立性,但是在高并发的情况下出现了数据库连接获取不到的问题

问题症状

当出现较大并发访问系统时,比如30并发,则会出现以下错误

CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool1 - Connection is not available, request timed out after 30000ms.

获取数据库连接的时间居然超过了30秒,正常情况下一个请求的处理时间是200ms,所以觉得特别奇怪。 按说即使数据库连接数小于请求并发数,因为数据库连接是共享的,请求也可以很快地获取到数据库连接并完成请求。但是实际却超过了30秒。

查看连接池情况

  • 从连接池信息可以看出,当请求并发量很大时,连接数确实不够。而且最大连接数已经是100(默认是10)
HikariPool1 - Before cleanup stats (total=22, active=0, idle=22, waiting=0)
HikariPool1 - Before cleanup stats (total=49, active=49, idle=0, waiting=87)
oikariPool1 - Before cleanup stats (total=100, active=100, idle=0, waiting=70)
HikariPool1 - Before cleanup stats (total=100, active=0, idle=100, waiting=0)
HikariPool1 - Before cleanup stats (total=100, active=0, idle=100, waiting=0)

查看日志,觉得更加奇怪了,请求并发数是30,连接数已经是100了,但是还是不够。 所以怀疑连接泄漏了。但是使用的Spring Boot来管理连接池,并不是手动使用连接池,应该不可能是泄漏了。

查看请求日志

  • 查看一个请求中数据库连接相关的日志
Acquired Connection [HikariProxyConnection@880338874 wrapping com.mysql.cj.jdbc.ConnectionImpl@332c8129] for JDBC transaction
Switching JDBC Connection [HikariProxyConnection@880338874 wrapping com.mysql.cj.jdbc.ConnectionImpl@332c8129] to manual commit
...
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@726cda03]
Acquired Connection [HikariProxyConnection@1154774545 wrapping com.mysql.cj.jdbc.ConnectionImpl@4664e639] for JDBC transaction
Switching JDBC Connection [HikariProxyConnection@1154774545 wrapping com.mysql.cj.jdbc.ConnectionImpl@4664e639] to manual commit

...
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@571ff6bc]
Acquired Connection [HikariProxyConnection@356317124 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e0e3cb5] for JDBC transaction
Switching JDBC Connection [HikariProxyConnection@356317124 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e0e3cb5] to manual commit

从这个日志中Transaction synchronization suspending SqlSession可以看到,因为使用Spring事务的REQUIRES_NEW传播类型,导致一个请求会同时占用多个连接,没有释放。这样就可能导致获取连接的死锁

解决办法

  • 设置连接超时时间,当获取连接的时间超过阈值时,就会退出事务,释放事务占用的连接。这样就可以破坏死锁条件spring.datasource.hikari.connection-timeout: 3000

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券