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

2026-07-04 121 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个连接够用了。

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

相关文章

AI圈最近有点热闹!OpenClaw又整活了,以及那些让我欲罢不能的新玩具
写API这件事,我踩过的坑比吃过的盐还多
重试:本以为是救命稻草,没想到是压死骆驼的最后一根稻草
你的Pod正在被”悄悄枪毙”:K8s资源压力下的驱逐机制全解
还在为部署AI工具秃头?小龙虾帮你一键搞定!
连上了就别断开:一次把HTTP长连接聊透

发布评论