连接池耗尽事故:finally块里的”安全代码”是怎么泄漏的

2026-07-04 3 0

连接池耗尽事故:finally块里的"安全代码"是怎么泄漏的

先说个真事:某天夜里服务报警MySQL连接数打满。查了半天,发现问题代码每次只漏一个连接,白天流量一大池子当场去世。根因藏在finally块里——一段看起来"绝对安全"的关闭代码。

泄漏是怎么发生的

看这段典型代码:

Connection conn = null;
PreparedStatement ps = null;
try {
    conn = dataSource.getConnection();
    ps = conn.prepareStatement(sql);
    rs = ps.executeQuery();
} finally {
    if (rs != null) rs.close();
    if (ps != null) ps.close();  // 问题在这!
    if (conn != null) conn.close();
}

finally不是必定执行吗?但你忽略了关键:ps.close()抛异常,conn.close()就不执行了。网络抖动时PS关闭失败,异常被catch吞掉,表面风平浪静,底下连接一个一个漏。

正确写法:try-with-resources

try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // ... 业务逻辑 ...
} catch (Exception e) {
    log.error("失败", e);
}
// 无需finally!close()失败自动追加到suppressed,不阻断其他资源

Java 7引入的try-with-resources,每个资源close()失败自动追加到suppressed列表,不阻断其他资源关闭。用了它基本告别连接泄漏。

必须手动管连接时的正确姿势

Connection conn = null;
try {
    conn = dataSource.getConnection();
} finally {
    if (conn != null)
        try { conn.close(); }
        catch (SQLException e) { log.error("关闭失败", e); }
}

只关conn,别关ps。JDBC规范:关闭Connection会级联关闭由它创建的所有Statement。先关ps失败会阻断conn关闭,只关conn是最安全的选择。

防泄漏三件套

1. 测试环境开泄漏检测:leak-detection-threshold设为30000,HikariCP会在30秒未归还时打日志。

2. 合理配置,别贪大:小服务10-20个连接够用,connection-timeout设30秒防无限等。

3. catch异常必须log:finally块里catch了异常却不log,是最危险的自欺欺人。

血的教训

finally块里catch异常然后静默,是最危险的自欺欺人。它让错误静默发生,问题难以定位。catch可以,log一定要打。

测试环境几乎不会复现这类问题。连接每次正常还,看不出任何毛病。但生产环境流量一冲,半夜没事,白天去世。

连接池不是越大越好。每个连接占MySQL资源,池太大会让MySQL疲于应付连接管理,性能反而下降。小服务10-20个连接够用了。

没做过生产故障复盘的代码,等于没写过。下期见。

相关文章

限流真不是加个计数器那么简单
OpenClaw 使用经验分享:一个话痨AI助手是怎么炼成的
API设计翻车实录:那些年我们一起踩过的坑
API设计翻车实录:那些年我们一起踩过的坑
为什么你的服务挂了,你却是最后一个知道的?——我见过最被低估的后端技能
为什么你的服务挂了,你却是最后一个知道的?——我见过最被低估的后端技能

发布评论