连接池泄漏原因

Posted

技术标签:

【中文标题】连接池泄漏原因【英文标题】:Connection Pool Leak Causes 【发布时间】:2017-02-15 08:17:31 【问题描述】:

我的连接池一直遇到(我认为)问题。 具体来说,我的日志显示了以下消息:

org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject$AbandonedObjectCreatedException: 以下代码创建的池化对象 [时间] 尚未返回到池中

我检查了日志显示的堆栈跟踪中列出的方法,但找不到罪魁祸首(我总是在每个方法的末尾关闭 ResultSetPreparedStatementConnection)。

我有一个执行两个查询的方法,也许我没有正确执行它。

它的布局如下:

ConnectionPool pool = ConnectionPool.getInstance();
Connection connection = pool.getConnection();
PreparedStatement ps = null;
PreparedStatement rowsPs = null;
ResultSet rs = null;
ResultSet rowsRs = null;

String query = "SELECT SQL_CALC_FOUND_ROWS ...";
String totalRowsQuery = "SELECT FOUND_ROWS() AS RowCount";

try 
    ps = connection.prepareStatement(query);
    [set ps params]
    rs = ps.executeQuery();
    [process rs]

    rowsPs = connection.prepareStatement(totalRowsQuery);
    rowsRs = rowsPs.executeQuery();
    [process rowsRs]
 catch (SQLException e) 
    [handle e]
 finally 
    DBUtil.closeResultSet(rs);
    [close rowsRs]
    [close ps]
    [close rowsPs]
    [close connection]

DBUtils 方法的一个例子是:

public static void closeResultSet(ResultSet rs)

    try
    
        if (rs != null)
            rs.close();
    
    catch (SQLException sqle)
    
        sqle.printStackTrace();
    

这种方法的总体布局看起来不错吗?我应该以不同的方式处理连接吗?还是其他导致错误被记录的方法?

谢谢。

其他信息

我也收到了SQLException

java.sql.SQLException: Connection com.mysql.jdbc.JDBC4Connection@[some number] is closed

在线:rowsPs = connection.prepareStatement(totalRowsQuery);

意味着在某处之前,连接已关闭。 我没有在任何地方明确关闭连接。 是否有可能调用的其他一些数据访问方法以某种方式关闭了此方法中的连接? (pool.getConnection() 打电话给dataSource.getConnection()

更新: 我已尝试按照建议使用 try-with-resources,但问题仍然存在。

上面第一个代码sn-p中引用的ConnectionPool类:

public class ConnectionPool 


    private static ConnectionPool pool = null;
    private static DataSource dataSource = null;

    public synchronized static ConnectionPool getInstance()
    

        if ( pool == null ) 
            pool = new ConnectionPool();
        
            return pool;
    

    private ConnectionPool()
    
        try 
            InitialContext ic = new InitialContext();
            dataSource = (DataSource) 
                    ic.lookup([jdbc/dbName]);
        

        catch (Exception e) 
            e.printStackTrace();
         
    

    public Connection getConnection()
    
        try 
            return dataSource.getConnection();
        
        catch (SQLException sqle) 
            sqle.printStackTrace();
            return null;
        
    
    public void freeConnection(Connection c)
     
        try 
            c.close();
        
        catch (SQLException sqle) 
            sqle.printStackTrace();
        
    

更多来源: 我的池资源元素:

<Resource auth="Container" driverClassName="com.mysql.jdbc.Driver"  
            logAbandoned="true" maxActive="100" maxIdle="30" maxWait="10000" 
            removeAbandonedOnBorrow="true" 
            removeAbandonedTimeout="60" type="javax.sql.DataSource" 
            testWhileIdle="true" testOnBorrow="true" 
            validationQuery="SELECT 1 AS dbcp_connection_test"/>

更新: 我已经打开了慢查询日志,但是尽管再次抛出Exceptions,慢查询日志没有记录任何内容(没有查询需要超过 10 秒)。

所以看起来查询花费的时间不超过 60 秒。

仍然不确定是什么原因造成的。

【问题讨论】:

如果finally 中的一行失败了怎么办?不会执行任何下游线路。我只是关闭连接,因为所有健全的 JDBC 实现都会级联关闭其所有相关资源。 好的,谢谢。但它并没有真正找到问题的根源。您是否看到 finally 子句中的行会失败的任何原因? 是的,每一行都可能抛出异常。那种原因。 我会在我的日志中看到这些异常,不是吗?我打印它们。所有资源都通过一个 util 类关闭,该类围绕 try 子句中的语句并在抛出异常时打印堆栈跟踪。 pool.getConnection()获得连接,用connection.close()释放连接,这是池的不变量。因此,您最好找出原因导致connection.close() 行未到达或因异常而失败。或者,作为一个更奇特的解释,变量connection 被重新分配,原来的连接被放弃了。 【参考方案1】:

这两行需要 60 多秒,因此连接池决定您的连接被放弃并关闭它。稍后您的代码尝试使用连接,但它已被连接池取消。

rs = ps.executeQuery();
[process rs]

既然您确定它不长 executeQuery(),请使用连接/线程/请求 ID 和 [process rs] 部分的时间进行调试打印。如果您看到这部分的时间超过 50 秒,您应该优化 [process rs] 或读取数据并将它们存储在内存中,然后再进行处理。

【讨论】:

【参考方案2】:

尝试将 removeAbandonedTimeout 值减小到 20 或 15 之类的值。您的 maxWait 为 10 秒,但如果连接被放弃,那么您将等待 60 秒再返回它们。

请注意,这不是解决方案,而只是测试您的连接是否真的挂起。

【讨论】:

【参考方案3】:

mysql 服务器是否超时连接?这也可能是内核问题,如果连接长时间处于空闲状态,则底层 tcp 连接可能会被终止。

【讨论】:

【参考方案4】:

作为使用池化 JDBC 连接时的一般经验法则:

    不要在每个连接上同时打开多个 ResultSet。在打开第二个之前关闭第一个 ResultSet 及其关联的 Statement。

    始终按照您创建资源的相反顺序关闭资源:create(s1)->execute(r1)->close(r1)->close(s1)->create(s2)->执行(r2)->关闭(r2)->关闭(s2)

    确保长时间运行的查询处理不超过池允许的最大连接寿命。

    即使您没有超过最大连接寿命,也可能由于网络链接中的小中断而丢失连接。

【讨论】:

1 和 2 我愿意。关于数字 3:我的查询都不会超过 1-2 秒。它们是否会比我运行它们花费更多的时间(它们基于我的服务器性能而不是客户端的性能,对吧)?关于数字 4:它似乎总是由于相同的方法而发生,这导致我认为这不仅仅是一些随机的中断。

以上是关于连接池泄漏原因的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server:跨池连接的隔离级别泄漏

weblogic连接池

labix.org/mgo 连接池泄漏问题

EF 6 连接池和存储的查询字符串 RAM 泄漏

JDBC07:数据库连接池

什么是数据库连接泄漏