【问题标题】:Connection close does not close the connections in Tomcat连接关闭不会关闭 Tomcat 中的连接
【发布时间】:2019-01-04 06:51:07
【问题描述】:

在我在 Tomcat 上运行的 java web 项目中,2 天后有超过 4000 个休眠连接(我使用 sp_who 命令检查了它们)。在完成数据库工作后,我会关闭每个语句、结果集和连接。我将以下模板用于数据库内容。

    try{
        this.openConnection();
        this.createStatement();

        // things ...

        this.cleanResources();
    }catch (SQLException e){
        this.cleanResources();
        e.printStackTrace();
    }

    public void cleanResources() {
    try {
        if (this.rs != null) {
            rs.close();
            this.rs = null;
        }
        if (this.stmt != null) {
            stmt.close();
            this.stmt = null;
        }
        if (this.conn != null) this.closeConnection();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (this.conn != null) this.closeConnection();
    }
}

   public void closeConnection() {
    try {
        if (this.conn != null)
            this.conn.close();
        this.isOpen = false;
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void createStatement() {
    try {
        if (!this.isOpen) this.openConnection();
        this.stmt = this.conn.createStatement();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void openConnection() {
    try {
        this.conn = ds.getConnection(); // ds is a javax.sql.DataSource instance
        this.isOpen = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
}

每次 sql 代码运行后,具有睡眠状态的行数确实会增加,并且数据库内容会变得非常慢。为什么会发生?完成后如何完全终止连接?真的是睡眠连接让我的 SQL Server 变慢了吗?

这是我的 Tomcat 配置(在 context.xml 中):

maxTotal="20" maxActive="20" maxIdle="20"

【问题讨论】:

  • 强烈建议您摆脱该代码并开始使用 try-with-resources。在字段中保留StatementResultSet 是不合适的。 --- 但是,如果你坚持这个逻辑,至少你需要把cleanResources() 放在一个finally 块中。
  • 既然我们看不到openConnection()closeConnection() 方法的作用,我们怎么能说你做对了吗?
  • @Andreas 对不起,我错过了那个,现在编辑了。
  • @Andreas 我现在将尝试在 finally 块中进行操作。
  • @Andreas 用 finally 块做这件事没有任何区别,仍然休眠的连接数在增加。

标签: java sql sql-server tomcat


【解决方案1】:

您用于清理连接的代码/模式存在缺陷:

try {
    this.openConnection();
    this.createStatement();

    // things ...

    this.cleanResources();
} catch (SQLException e){
    this.cleanResources();
    e.printStackTrace();
}

如果“事物”抛出一些不是SQLException 或子类的异常,这不会关闭连接。

如果应该是:

try {
    this.openConnection();
    this.createStatement();

    // things ...

    this.cleanResources();
} catch (SQLException e) {
    e.printStackTrace();
} finally {
    this.cleanResources();
}

然后这个:

public void cleanResources() {
    try {
        if (this.rs != null) {
            rs.close();
            this.rs = null;
        }
        if (this.stmt != null) {
            stmt.close();
            this.stmt = null;
        if (this.conn != null) this.closeConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

如果rs.close()stmt.close() 抛出异常,则连接不会关闭。

应该是这样的:

public void cleanResources() {
    try {
        if (this.rs != null) {
            rs.close();
            this.rs = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        if (this.stmt != null) {
            stmt.close();
            this.stmt = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        if (this.conn != null) this.closeConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在某些情况下,这些缺陷中的任何一个都可能导致数据库连接泄漏。

另一种可能性是您的代码中的某个地方没有遵循您的(有缺陷的)模式。

我认为您需要阅读:

  • Java 7+“尝试资源”支持
  • Java 1.0+“尝试...最终”。

还值得注意的是,以下做法是不好的做法:

  • 在大多数情况下捕获Exception ...
  • 挤压异常(即捕获并静默继续)...在大多数情况下
  • 在整个代码中分散调用 printStackTrace。

【讨论】:

  • 我观察了一下,即使没有异常,睡眠连接数也增加了。我只是尝试在 finally 块中清理资源,但没有任何区别。
  • 在这种情况下你做错了。 “最终”或“尝试使用资源”的方法会起作用……如果你做得正确的话。
  • 如果你真的需要帮助调试你的代码,你需要提供一个 MCVE(强调 M ... for Minimal)