连接池泄漏最直接信号是连接数随时间单向增长且重启后归零;通过show status和processlist检查sleep连接、设置leakdetectionthreshold配合wait_timeout、用try-with-resources确保释放、调低mysql超时时间促发报错,可快速定位泄漏。

怎么快速确认是不是连接池泄漏了
看连接数是否随时间单向增长,且重启应用后归零——这是最直接的信号。别等服务挂了才查,先用 SHOW STATUS LIKE 'Threads_connected' 看当前活跃连接数,再对比 SHOW VARIABLES LIKE 'max_connections',如果前者长期接近后者(比如持续 >80%),基本可以判定有问题。
更准一点的做法是:在应用空闲时执行 SELECT * FROM information_schema.processlist WHERE USER = 'your_app_user' AND COMMAND = 'Sleep' AND TIME > 60。出现大量超 60 秒的 Sleep 连接,且线程 ID 不断新增,大概率就是没 close() 或没 release()。
HikariCP 的 leakDetectionThreshold 怎么设才有效
这个参数不是越小越好,也不是开就完事。它本质是“从连接被借出开始计时,超时未归还就打堆栈日志”,但前提是你的代码真用了连接池获取连接,而不是绕过池直连(比如误用 DriverManager.getConnection())。
-
leakDetectionThreshold=30000(30 秒)适合开发/测试环境,能快速暴露问题 - 生产环境建议设为
60000(60 秒)或更高,避免误报;但必须配合 MySQL 的wait_timeout=60,否则连接池以为还在用,MySQL 已经断开了,反而掩盖泄漏 - 日志里一旦出现
HikariPool-1 - Connection leak detection triggered,立刻按堆栈找对应代码行,重点查getConnection()后有没有配对的close()或try-with-resources
Java 里哪些写法看着正常,其实一定会泄漏
最常见的“伪安全”写法,表面有 finally,实际逻辑没覆盖所有分支;或者用了连接池却手动关了底层 socket。
- 写成
Connection conn = dataSource.getConnection(); ... conn.close();—— 没 try/catch/finally,异常一抛,close()就跳过了 - 用
try { ... } finally { if (conn != null) conn.close(); }—— 看似稳妥,但如果conn.close()自己抛异常(比如网络闪断),后续资源(Statement、ResultSet)就彻底漏了 - 手动调
conn.getMetaData().getConnection().close()或类似反射操作 —— 直接破坏连接池管理,连接永远回不到池里 - 正确姿势只有一条:
try (Connection conn = ds.getConnection(); Statement stmt = conn.createStatement()) { ... },JVM 保证退出时自动释放
MySQL 层面能帮上什么忙
数据库本身不解决泄漏,但能帮你更快暴露和定位。关键不是“防”,而是“让泄漏显形”。
- 把
wait_timeout和interactive_timeout都设成60(单位秒),这样空闲连接 60 秒后 MySQL 主动断开,应用侧会收到Communications link failure,倒逼你去修代码,而不是让连接一直挂着占坑 - 定期跑
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.processlist ORDER BY TIME DESC LIMIT 20,重点关注TIME大于 60 且COMMAND是Sleep的记录 - 别依赖
show processlist实时抓包——它只反映瞬间快照;要持续观察,就得用 Prometheus 抓mysql_global_status_threads_connected指标,画趋势图
真正难的不是发现泄漏,而是确认某次请求路径里哪一行没关连接。堆栈日志里看到 getConnection() 调用点,往往离真实泄漏点隔了两层异步回调或事务拦截器——这时候得结合 AOP 或字节码插桩,盯住 checkout 和 checkin 事件。











