数据库连接池泄露检测:连接未归还导致耗尽?HikariCP leakDetectionThreshold 精准定位!
运营说后台管理页面打不开了。查日志,全是 HikariPool-1 - Connection is not available, request timed out after 30000ms。连接池配了 20 个连接,监控显示全部是 active,一个 idle 都没有。等了十分钟也没恢复——不是流量高,是连接被人借走没还。重启能好,但过两天又犯。
连接泄露比内存泄露更难排查。内存泄露至少还有 heap dump 可以分析,连接泄露你只能看到连接池满了,但看不到是谁借了没还、从哪里借的。等你发现的时候,池子已经空了,所有需要数据库的请求都在排队等超时。
今天聊聊怎么用 HikariCP 自带的一个配置项,把连接泄露的元凶精准揪出来。
连接是怎么被"偷"走的
正常情况下的连接使用流程是这样的:
借连接 → 执行 SQL → 还连接
Spring 的 @Transactional 和 JdbcTemplate 会帮你自动管理这个过程。方法结束,事务提交或回滚,连接自动归还池子。
但有些写法会悄悄地把连接"偷"走:
经典泄露场景:Stream 没关
// ❌ 连接泄露
jdbcTemplate.queryForStream(sql, (rs, i) -> {
return convert(rs);
}).filter(...).collect(...);
// 这个 Stream 底层持有一个数据库连接
// 如果 Stream 没有被正确关闭,连接永远不会归还
queryForStream 返回的 Stream 对象背后是一个还没有关闭的 ResultSet,而 ResultSet 背后是一个还没有归还的连接。如果在这个 Stream 上做了复杂操作,抛了异常导致 Stream 没关,连接就再也回不去了。
经典泄露场景:finally 里没关
// ❌ 手动管理连接,但忘了关
Connection conn = null;
try {
conn = dataSource.getConnection();
// ... 执行 SQL
// 中间抛了异常,直接跳到 catch
conn.close(); // 这行没执行到
} catch (Exception e) {
log.error("出错", e);
// 忘了在这里也关连接
}
裸写 JDBC 的时候最容易出这种问题。好在现在 Spring 帮我们封装了大部分场景,但如果你在项目里找到了 dataSource.getConnection() 的裸调用,建议重点排查。
经典泄露场景:事务超长
@Transactional
public void processBatch() {
// 开启事务,借走一个连接
for (Item item : hugeList) { // 10 万条数据
calculate(item); // 每条 50ms
saveToDb(item); // 连接一直被占用
}
// 事务提交,连接归还 —— 但已经过了 5000 秒
}
这个场景严格来说不是泄露——连接最终会还回去。但如果事务时间太长,在这个时间窗口内其他请求拿不到连接,现象跟泄露一模一样。
HikariCP 的 leakDetectionThreshold 是怎么工作的
HikariCP 有一个配置项专门干这件事:leakDetectionThreshold。
spring:
datasource:
hikari:
leak-detection-threshold: 10000 # 毫秒
它的含义是:如果一个连接被借出去超过这个时间还没归还,HikariCP 就会在日志里打一条 WARN,并且附上这个连接是在哪里被借走的。
日志长这样:
WARN HikariPool-1 - Connection leak detection triggered
for connection 12345, stack trace follows:
java.lang.Exception: Apparent connection leak detected
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(...)
at com.example.service.OrderService.processOrder(OrderService.java:42)
at com.example.controller.OrderController.create(OrderController.java:15)
...
注意最后两行——它告诉你是 OrderService.processOrder 这个方法的第 42 行借走的连接一直没还。直接定位到具体代码,不用猜。
这是一个零成本的检测手段。它在连接被借出的时候记录一个时间戳和堆栈信息,然后后台线程定期扫描,发现有超过阈值的就打日志。不影响连接池的正常运行,只在检测到异常时告警。
怎么配置最合适
leakDetectionThreshold 设多少,取决于你的业务场景。
设太高了没用
设成 30000ms。但你的接口正常情况下 50ms 就返回了。那泄露发生了至少 30 秒才能被发现,这 30 秒内连接池可能已经耗尽了。
设太低了误报多
设成 1000ms。结果有个报表接口正常就要跑 2 秒,每次跑都打一条 WARN。久而久之大家就无视这个告警了——狼来了效应。
建议值
一般设成正常接口耗时的 3~5 倍。比如你的接口 P99 延迟是 500ms,设成 2000ms 比较合理。这样正常情况下不会触发,但泄露发生了能在 2 秒内发现。
如果业务里有慢查询(比如定时任务批量处理),可以配两个数据源,快业务的连池设短阈值,慢业务的设长阈值。
不只是检测——如何主动预防
leakDetectionThreshold 是事后发现,更重要的是从源头上杜绝泄露。
永远不要裸用 Connection
// ❌
Connection conn = dataSource.getConnection();
// ✅ 交给 Spring 管理
@Transactional
public void doSomething() {
jdbcTemplate.query(...);
}
只要你不手动 getConnection(),Spring 的事务管理器会确保连接在事务结束时归还。没有裸 Connection,就没有泄露。
Stream 必须 try-with-resources
// ❌ 泄露
Stream<Entity> stream = jdbcTemplate.queryForStream(...);
// ✅ try-with-resources 保证关闭
try (Stream<Entity> stream = jdbcTemplate.queryForStream(...)) {
stream.filter(...).collect(...);
}
queryForStream 返回的 Stream 实现了 AutoCloseable,用 try-with-resources 包裹,即使中间抛异常也能保证关闭。
配置连接池的最大生命周期
spring:
datasource:
hikari:
max-lifetime: 1800000 # 30 分钟,连接的最长存活时间
idle-timeout: 600000 # 10 分钟,空闲连接的超时时间
connection-timeout: 30000 # 30 秒,等待连接的最长时间
max-lifetime 是一个兜底机制。如果一个连接因为某种原因泄露了(bug、框架 bug),至少它会在 30 分钟后被强制收回。不能解决问题,但能防止池子被永久耗尽。
实战:排查流程
当线上出现 Connection is not available 的报错时:
Step 1: 看监控
→ HikariCP 的 active 连接数是否打满?
→ 打满了多少秒?
Step 2: 开 leakDetectionThreshold
→ 设 10000ms,重启(或者在配置中心动态下发)
→ 等下一次泄露发生
Step 3: 看日志
→ 搜索 "Connection leak detection triggered"
→ 定位堆栈中的业务代码位置
Step 4: 修复
→ Stream 没关?加 try-with-resources
→ 事务太长?拆成小批,或者把非 DB 操作移出事务
→ 手动借了没还?删掉手动管理,交给 Spring
走完这四步,还没有搞不定的连接泄露问题。
总结
连接池泄露这件事,最难的不是修,是找。连接池满了只是一个表象,真正的原因可能藏在任何一个借走连接没还的代码角落里。
leakDetectionThreshold 的价值就在于——它把"连接池满了"这个结果,反向追踪到了"是哪一行代码借的没还"。从现象到根因,一步到位。
几个操作建议:
- 生产环境把这个配置打开,设一个合理的阈值。它是 HikariCP 内置的,零额外依赖,不影响性能。
- 日志里搜到泄露堆栈后,照着改代码,别只重启了事。重启只能暂时清空池子,下次还会犯。
- 长远来看,杜绝裸
Connection、Stream 加 try-with-resources、大事务拆分,这三件事做到位,连接泄露的发生概率可以压到零。
有用的话转给你们组里还在每次连接池耗尽就重启的同事。
标题:数据库连接池泄露检测:连接未归还导致耗尽?HikariCP leakDetectionThreshold 精准定位!
作者:jiangyi
地址:http://jiangyi.space/articles/2026/06/07/1780754053737.html
公众号:服务端技术精选
评论