Spring Data JDBC PostgreSQL事务中连接泄漏问题排查求助
Spring Data JDBC PostgreSQL连接延迟释放问题排查
问题现象
- 非事务方法的数据库连接能正常获取并归还连接池,但标注
@Transactional的事务方法调用时,连接无法及时释放,触发HikariCP连接泄漏告警。 - 矛盾点:告警触发时,连接池统计显示所有连接均为空闲状态(
total=16, active=0, idle=16)。 - 业务逻辑仅需毫秒级完成,但连接要在30秒后才归还至连接池,延迟时间与HikariCP的
leakDetectionThreshold(30秒)接近。
配置类代码
@Configuration @EnableJdbcRepositories("com.api.repositories") public class DBConfig { @Autowired private DbProps dbProps; @Bean public DataSource readWriteDataSource() { LOGGER.info("DB Props: {}",dbProps.toString()); PGSimpleDataSource dataSource = new PGSimpleDataSource(); dataSource.setUrl(dbProps.getDbUrl()); dataSource.setUser(dbProps.getDbUser()); dataSource.setPassword(dbProps.getDbPassword()); dataSource.setLogUnclosedConnections(true); return connectionPoolDataSource(dataSource); } protected HikariConfig hikariConfig(DataSource dataSource) { HikariConfig hikariConfig = new HikariConfig(); int cpuCores = Runtime.getRuntime().availableProcessors(); hikariConfig.setMaximumPoolSize(cpuCores*4); hikariConfig.setDataSource(dataSource); hikariConfig.setIdleTimeout(600000); hikariConfig.setLeakDetectionThreshold(30000); hikariConfig.setConnectionTimeout(30000); hikariConfig.setMaxLifetime(1800000); hikariConfig.setAutoCommit(false); return hikariConfig; } protected HikariDataSource connectionPoolDataSource( DataSource dataSource) { return new HikariDataSource(hikariConfig(dataSource)); } @Bean public PlatformTransactionManager transactionManager(DataSource readWriteDataSource) { return new DataSourceTransactionManager(readWriteDataSource); } @Bean public NamedParameterJdbcOperations usersJdbcOperations(DataSource readWriteDataSource) { return new NamedParameterJdbcTemplate(readWriteDataSource); } }
事务方法代码
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) public String placeBet(PlaceBet placebet) { try { Bet unsavedBet = Bet.builder().amount(BigDecimal.valueOf(placebet.amount())) .coin(placebet.coin()) .game(placebet.game()) .gid(placebet.gid()) .uid(placebet.uid()) .name(placebet.name()) .created(LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)) .originalOdd(BigDecimal.valueOf(placebet.original_odd())).build(); Bet savedBet = betRepository.save(unsavedBet); if (null != savedBet) { LOGGER.info("saved Bet: {}",savedBet.toString()); } return "success"; } catch (Exception e) { LOGGER.error("Error {} encountered saving bet: {} changes rolled Back",e.getMessage(),placebet.toString()); return "bet error"; } }
连接泄漏告警日志
2023-01-20 00:11:15 WARN ProxyLeakTask.java:84 - 检测到连接泄漏:线程http-nio-8001-exec-11持有的Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49,堆栈跟踪如下 java.lang.Exception: 检测到明显的连接泄漏 at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100) at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:265) at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400) at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) at com.api.services.BetService$$EnhancerBySpringCGLIB$$23929008.placeBet(<generated>) at com.api.controllers.BetController.placeNewBet(BetController.java:132) at jdk.internal.reflect.GeneratedMethodAccessor89.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568)
连接池统计日志
2023-01-20 00:11:58 DEBUG HikariPool.java:421 - HikariPool-1 - 连接池统计:总数=16,活跃=0,空闲=16,等待=0 2023-01-20 00:11:58 DEBUG HikariPool.java:518 - HikariPool-1 - 跳过填充连接池,当前连接数已足够
连接延迟释放日志
2023-01-20 00:11:15 WARN ProxyLeakTask.java:84 - 检测到连接泄漏:线程http-nio-8001-exec-11持有的Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49,堆栈跟踪如下 2023-01-20 00:11:56 DEBUG DataSourceTransactionManager.java:330 - 提交JDBC事务,连接为[HikariProxyConnection@304420024 wrapping Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49] 2023-01-20 00:11:56 DEBUG DataSourceUtils.java:242 - 将JDBC连接[HikariProxyConnection@304420024 wrapping Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49]的隔离级别重置为2 2023-01-20 00:11:56 DEBUG DataSourceTransactionManager.java:389 - 事务结束后释放JDBC连接[HikariProxyConnection@304420024 wrapping Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49] 2023-01-20 00:11:56 INFO ProxyLeakTask.java:91 - 之前报告的泄漏连接(线程http-nio-8001-exec-11持有的Pooled connection wrapping physical connection org.postgresql.jdbc.PgConnection@6fae4b49)已归还至连接池(无泄漏)
排查方案
1. 验证事务代理是否生效
事务方法延迟释放连接的常见原因是事务切面未正确生效,导致事务上下文未被管理:
- 检查
placeBet方法是否被同类内部方法调用(比如BetService中另一个非事务方法调用placeBet),这种情况Spring CGLIB代理无法拦截,事务不会被正确提交/回滚,连接会绑定在线程上下文直到线程被回收。 - 确认控制器
BetController注入的是Spring容器管理的BetService代理对象,而非手动new的实例。
2. 排查线程阻塞问题
从日志看,事务提交比业务逻辑完成晚了41秒,说明线程在事务方法执行完毕后被阻塞:
- 在告警触发(00:11:15)到连接释放(00:11:56)之间,用
jstack <PID>抓取线程栈,查看http-nio-8001-exec-11线程的状态:- 是否卡在锁等待、文件IO、远程调用等耗时操作?
- 是否存在自定义AOP切面在事务切面之后执行,且切面逻辑中有阻塞行为?
3. 检查HikariCP自动提交配置
配置中设置了hikariConfig.setAutoCommit(false);,需确认:
- Spring事务管理是否正确接管了事务提交:从日志看最终执行了commit,但延迟严重,检查是否有代码在事务方法外手动操作连接的提交状态。
- 临时将
autoCommit改为true测试(Spring事务方法内会自动将连接设为autoCommit=false,事务结束后恢复),观察连接释放是否恢复正常。
4. 验证Spring事务同步机制
- 检查是否存在自定义的
TransactionSynchronization实现,在事务完成后执行了耗时操作,导致连接无法及时释放。 - 确认
DataSourceTransactionManager是否有自定义扩展,比如重写了doCommit或doCleanupAfterCompletion方法,引入了延迟逻辑。
5. 数据库端连接状态排查
用PostgreSQL命令查询连接状态:
SELECT pid, state, query, backend_start FROM pg_stat_activity WHERE pid = '<连接对应的PID>';
确认在告警期间,该连接是处于active还是idle状态,判断是应用端未释放连接,还是数据库端导致的阻塞。
6. 解析连接池统计矛盾点
连接池统计显示active=0但实际连接未释放,是因为HikariCP的active统计仅跟踪直接调用getConnection()且未close()的连接,而Spring事务通过DataSourceUtils将连接绑定到线程上下文,HikariCP无法感知这种绑定。可以通过以下方式验证:
- 启用Spring的
DataSourceUtils日志(设置org.springframework.jdbc.datasource.DataSourceUtils为DEBUG级别),查看连接绑定和释放的详细流程。
内容的提问来源于stack exchange,提问作者martin kimani
相关产品推荐
相关产品推荐

