You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Connection.close()未正确关闭连接致数据库连接耗尽问题求助

排查连接耗尽问题的思路与解决方案

我来帮你排查下这个连接耗尽的问题——明明调用了close()且connection.isClosed()返回true,却还是出现连接耗尽,大概率是代码里的细节漏洞或者JDBC使用方式的问题,咱们一步步来梳理:

1. 重复注册Driver的隐患

你的getConnection方法里每次获取连接都手动注册Driver:

Driver driver = (Driver) Class.forName(clazz).newInstance();
DriverManager.registerDriver(driver);

这会导致Driver被多次注册,干扰DriverManager的连接管理逻辑。而且JDBC 4.0+版本(对应JDK 1.6+)已经支持自动加载Driver,只要驱动包在classpath里,DriverManager会自动识别并加载,完全不需要手动实例化和注册。

修改后的getConnection方法可以简化成这样:

private Connection getConnection(JspWriter out, String host, String dbname, String port, String user, String pwd) {
    try {
        if (host == null || dbname == null) {
            throw new IllegalArgumentException("Invalid host or dbname");
        }
        String url = "jdbc:mysql://" + host + ":" + port + "/" + dbname;
        // 移除手动Driver注册逻辑,JDBC会自动处理
        return DriverManager.getConnection(url, user, pwd);
    } catch (Exception e) {
        // 改用System.err输出错误,同时把错误反馈到页面方便调试
        System.err.println("Exception occurred while getting connection: " + e);
        try {
            out.println("Connection error: " + e.getMessage());
        } catch (IOException ioEx) {
            ioEx.printStackTrace();
        }
    }
    return null;
}

2. 手动关闭资源的潜在漏洞

你在finally块里手动关闭ResultSet、PreparedStatement和Connection,但这种方式容易因为异常嵌套导致资源未被正确关闭。比如如果rs.close()抛出异常,后续的st.close()和connection.close()会不会被执行?虽然你用了独立的try-catch,但Java 7+提供的try-with-resources语法能更可靠地自动关闭所有实现AutoCloseable接口的资源,从根源上避免遗漏。

重写后的executeQuery方法可以改成这样:

private JSONArray executeQuery(JspWriter out, Connection connection, String query, Object[] params) throws Exception {
    if (connection == null) {
        return null;
    }

    // try-with-resources会自动关闭st和rs,无需手动处理
    try (PreparedStatement st = connection.prepareStatement(query)) {
        int c = 1;
        for (Object param : params) {
            st.setObject(c++, param);
        }
        try (ResultSet rs = st.executeQuery()) {
            ResultSetMetaData meta = rs.getMetaData();
            List<String> columns = getColumnNames(meta);
            JSONArray json = new JSONArray();
            while (rs.next()) {
                JSONObject obj = new JSONObject();
                for (String column : columns) {
                    obj.put(column, rs.getObject(column));
                }
                json.put(obj);
            }
            return json;
        }
    } catch (Exception e) {
        System.err.println("Exception occurred while executing query: " + e);
        try {
            out.println("Query error: " + e.getMessage());
        } catch (IOException ioEx) {
            ioEx.printStackTrace();
        }
        // 不要吞掉异常,抛出给调用方处理,方便排查问题
        throw e;
    } finally {
        // 确保Connection被关闭,这里用独立的try-catch避免异常扩散
        try {
            if (!connection.isClosed()) {
                connection.close();
                System.out.println("Closed connection successfully: " + connection.hashCode());
            }
        } catch (Exception ex) {
            System.err.println("Failed to close connection: " + ex);
        }
    }
}

3. 添加连接生命周期日志追踪

为了确认是否真的有连接泄漏,你可以在代码里添加日志,追踪每个连接的创建和关闭:

  • 在getConnection方法返回连接时,打印:System.out.println("Acquired connection: " + connection.hashCode());
  • 在finally块关闭连接时,打印:System.out.println("Closed connection: " + connection.hashCode());

这样你就能在日志里看到每个连接的hashCode,对比创建和关闭的数量,如果创建数远大于关闭数,就能定位到哪里的连接没有被关闭。

4. 改用连接池管理连接

直接用DriverManager获取连接是非常低效的,而且手动管理连接很容易出现泄漏。建议改用成熟的连接池(比如HikariCP、C3P0),连接池会自动负责连接的分配、复用和回收,避免手动管理的风险。

比如用HikariCP的话,只需要配置数据源,然后从数据源获取连接,用完后调用close()(实际是把连接放回池里,不是真的关闭),代码会更简洁可靠。

总结

先从移除重复注册Driver的代码开始,然后改用try-with-resources确保资源关闭,再添加日志追踪连接生命周期,最后考虑切换到连接池,应该就能解决连接耗尽的问题。

内容的提问来源于stack exchange,提问作者user7665040

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:43:04