大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 摘要
- 描述:为什么连接池说我的连接泄漏了?
- 什么叫连接泄漏?
- 题解答案:如何彻底解决连接池泄漏?
- 1. 使用 try-with-resources 自动关闭连接(最关键)
- 2. 检查连接池最大连接数,避免业务量增长导致耗尽
- 3. 开启 leakDetectionThreshold,让连接池自动“报警”
- 代码解析:一个典型的泄漏案例(错误示例)
- 正确示例 Demo:用 try-with-resources 自动关闭资源
- 启用 leakDetectionThreshold:让 HikariCP 自动抓到泄漏点
- 最大连接数问题:为什么连接泄漏看起来像“数据库压力太大”?
- 实际场景分析:哪些地方最容易发生泄漏?
- 场景 1:在循环中不断创建连接
- 场景 2:异常抛出时 finally 被遗漏
- 场景 3:使用多层封装(容易忘记关闭)
- 场景 4:并发任务中手动开启连接,但不关闭
- 示例测试与结果
- 总结
前言
在后端项目里,数据库连接池泄漏几乎是所有人都踩过的坑。
一旦发生泄漏,症状非常典型:
- 系统运行一段时间后请求突然变慢
- 数据库连接数飙升到 max active
- 错误日志出现 “Too many connections”
- 必须重启服务才能恢复
最让人头疼的是:问题通常不是数据库本身,而是代码里有连接没被关闭—— 结果池子永远等不到归还,连接越用越少。
这篇文章我会讲清楚:
- 为什么连接池会泄漏
- 真实项目里的常见场景
- 如何让 HikariCP / DBCP 自动帮你找出泄漏点
- 可运行 Demo + 实战级代码解析
如果你也遇到过“线上数据库突然卡死,重启就好了”的情况,这篇文章非常值得收藏。
摘要
数据库连接池泄漏本质上是:连接被借出(getConnection)后没有被正确关闭(close)。
文章将从代码层面、连接池配置、监控机制三个角度来定位和解决问题。提供可运行 Demo(含 try-with-resources 正确写法!),以及 leakDetectionThreshold 和最大连接数配置的最佳实践。
描述:为什么连接池说我的连接泄漏了?
什么叫连接泄漏?
非常简单:
借出去的连接,没有再还回去。
连接池不是无限的,默认提供 10~50 个连接。如果你有一段代码没有关闭连接,那么每执行一次,连接池就少一个可用连接。
当连接数被占满后,后续请求就会卡死或直接报异常。
题解答案:如何彻底解决连接池泄漏?
解决方案大致分三步:
1. 使用 try-with-resources 自动关闭连接(最关键)
Java 中连接、Statement、ResultSet 都是 AutoCloseable,可以用 try-with-resources 自动 close。
2. 检查连接池最大连接数,避免业务量增长导致耗尽
例如 HikariCP 的maximumPoolSize,太小容易阻塞,太大又浪费资源。
3. 开启 leakDetectionThreshold,让连接池自动“报警”
这是 HikariCP 的神器配置:
只要某个连接超过一定时间没关闭,它就会打印堆栈信息,告诉你是哪一行代码泄漏的。
代码解析:一个典型的泄漏案例(错误示例)
下面的代码很多人都会这么写,但这是导致泄漏的元凶之一:
publicUsergetUser(intuserId)throwsSQLException{Connectionconn=dataSource.getConnection();// 借出连接PreparedStatementstmt=conn.prepareStatement("SELECT * FROM user WHERE id=?");stmt.setInt(1,userId);ResultSetrs=stmt.executeQuery();if(rs.next()){returnnewUser(rs.getInt("id"),rs.getString("name"));}// 问题:这里没有关闭任何资源!returnnull;}问题:
- conn 没有关闭
- stmt 没有关闭
- rs 没有关闭
- 意味着连接池永远等不到这个连接回收
你的系统最多只需要几十次调用就会把连接全占满。
正确示例 Demo:用 try-with-resources 自动关闭资源
这是连接池官方推荐的最佳写法,绝对不会泄漏:
publicUsergetUser(intuserId){Stringsql="SELECT * FROM user WHERE id=?";try(Connectionconn=dataSource.getConnection();// 自动关闭PreparedStatementstmt=conn.prepareStatement(sql);){stmt.setInt(1,userId);try(ResultSetrs=stmt.executeQuery()){// 自动关闭if(rs.next()){returnnewUser(rs.getInt("id"),rs.getString("name"));}}}catch(SQLExceptione){thrownewRuntimeException("Query error",e);}returnnull;}为什么安全?
- try(…) 自动调用 close()
- 不需要手写 finally
- 每一层资源都被正确回收
- 不管异常是否发生,连接都不会泄漏
这是避免泄漏的最终方案。
启用 leakDetectionThreshold:让 HikariCP 自动抓到泄漏点
在开发环境或测试环境中,你可以开启这个配置:
spring:datasource:hikari:leak-detection-threshold:2000# 超过2秒未关闭就打印堆栈或 Java 配置:
HikariConfigconfig=newHikariConfig();config.setLeakDetectionThreshold(2000);// 2秒运行后,如果某个连接超过 2 秒没关闭,会看到类似日志:
Connection leak detected: connection com.mysql.jdbc.JDBC4Connection@xxxx was not closed. Stack trace: at com.xxx.Repository.getUser(Repository.java:42) at com.xxx.Service.call(Service.java:25)看到这一段你就知道泄漏在哪里了,比你人工排查有效一百倍。
最大连接数问题:为什么连接泄漏看起来像“数据库压力太大”?
很多时候我们误以为数据库崩了,其实是连接池被耗尽了。
比如:
maximumPoolSize:10在这个配置下:
- 10 个连接都泄漏
- 第 11 个请求开始 → 直接阻塞或报错
这也是为什么:
- 重启服务 → 自动释放连接 → 一切恢复正常
- 跑一段时间 → 又挂了
如果你有大量并发接口访问数据库,一定要适当调高连接池大小:
maximumPoolSize:30minimumIdle:5但不要盲目调太高,否则数据库本身会被拖垮。
实际场景分析:哪些地方最容易发生泄漏?
场景 1:在循环中不断创建连接
for(...){Connectionconn=dataSource.getConnection();// 未关闭...}很快连接池耗尽。
场景 2:异常抛出时 finally 被遗漏
try{Connectionconn=ds.getConnection();...if(error)thrownewRuntimeException();}catch(Exceptione){log.error(e);}// 没有 finally,资源永远泄漏场景 3:使用多层封装(容易忘记关闭)
例如 Service → Repository → Helper → DAO
每一层都可能忘记关闭 ResultSet 或 Statement。
场景 4:并发任务中手动开启连接,但不关闭
线程池 + 手写 JDBC 特别容易踩坑。
示例测试与结果
使用错误代码 + 启用 leakDetectionThreshold,会得到:
Connection leak detected ... at com.xxx.Repository.getUser(Repository.java:42)使用 try-with-resources 改写,并开启 Hikari 日志:
- 连接借出、归还都正常
- 系统运行稳定
- 不会再出现连接耗尽、阻塞、重启恢复等问题
这是最可靠的验证方式。
总结
数据库连接池泄漏本质上是连接借出后没有关闭,导致连接池永远无法回收资源。
要彻底解决这个问题,你需要做到:
- 优先使用 try-with-resources,自动关闭所有 JDBC 资源
- 合理配置最大连接数,避免高峰期被耗尽
- 开启 leakDetectionThreshold,让连接池帮你自动排查泄漏堆栈
只要这三步做到位,你的数据库连接池就能保持长期稳定,不会再出现“运行 10 分钟就卡死”这种恼人的情况。