项目上线后跑着跑着突然变慢,甚至直接卡死,查了一圈才发现数据库连接数爆了。这种情况很可能是连接池里的连接没被正确释放,导致资源耗尽。别急,这问题不少人都踩过坑,咱们一步步来看。
啥是连接池?为啥要管它
连接池就像餐厅的餐具储备区——你不用每次吃饭都去烧水消毒碗筷,而是从池子里直接拿一个干净的用,用完再还回去。程序里也一样,数据库连接创建成本高,所以用连接池提前建好一批,随用随取。但关键在于:用完得还回去,不然下一个人就没得用了。
连接拿了不还,后果很直接
最常见的表现就是系统刚开始挺快,运行几小时后响应越来越慢,最后报“获取连接超时”或者“连接池已满”。这时候看数据库实际连接数,可能远高于正常值。比如你设了最大20个连接,结果发现有30多个连着,基本就能确定有连接没归还。
代码里最容易漏掉的地方
很多人写代码时只记得打开连接,却忘了关。尤其是在异常场景下,try 没 catch 好,connection.close() 就被跳过了。比如下面这段常见的错误写法:
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 如果这里抛异常,conn 根本没机会 close
while(rs.next()) {
System.out.println(rs.getString("name"));
}
conn.close(); // 风险点:前面出错就执行不到
正确的做法是把释放操作放在 finally 块里,或者直接用 try-with-resources,让 JVM 自动处理:
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
while(rs.next()) {
System.out.println(rs.getString("name"));
}
} // 自动关闭,不用担心遗漏
框架用得好,也能踩坑
有些人觉得用了 Spring 或 MyBatis 就万事大吉,其实不然。比如在 Spring 里手动获取连接但没交给事务管理,或者在异步任务里开了新线程操作数据库,但没配置传播行为,都有可能导致连接无法正常归还。
还有种情况是,查询结果集太大,fetchSize 没设,导致连接一直被占用读数据。比如一次查了几百万条记录,连接卡在读取状态,半天退不出来,池子很快就被占满。
怎么查是不是连接没释放
最简单的办法是打日志。可以在获取和归还连接时加日志,观察是否成对出现。也可以用 Druid 这类带监控的连接池,访问它的监控页面,直接看活跃连接列表,哪个 SQL 占着不放一目了然。
另外,Linux 下用 netstat 或 ss 命令也能看到应用到数据库的 TCP 连接数,持续增长不下降,基本就是泄漏了。
别等上线才救火
开发阶段就可以模拟高并发请求,跑个压力测试,看看连接数会不会越积越多。如果发现几分钟内连接数冲到上限还不降,赶紧回头查代码。早发现,早改,别等到用户投诉了再翻日志。
连接池不是魔法盒,它能提升性能,但前提是你会用。用完记得还,这是最基本的规矩。