Java ConnectionPool连接没有关闭,陷入'睡眠'

时间:2008-09-09 23:22:23

标签: java database tomcat

我有一个使用JNDI查找的webapp来获取与数据库的连接。

连接正常,返回查询没有问题。问题是连接没有正确关闭并且处于'睡眠'模式(根据mysql管理员)。这意味着它们变得无法使用,然后我的连接耗尽。

有人可以给我一些指示,说明我可以做些什么来使连接成功返回池中。

public class DatabaseBean {

private static final Logger logger = Logger.getLogger(DatabaseBean.class);

private Connection conn;
private PreparedStatement prepStmt;

/**
 * Zero argument constructor
 * Setup generic databse connection in here to avoid redundancy
 * The connection details are in /META-INF/context.xml
 */
public DatabaseBean() {
    try {
        InitialContext initContext = new InitialContext();
        DataSource ds = (DataSource) initContext.lookup("java:/comp/env/jdbc/mysite");
        conn = ds.getConnection();
    }
    catch (SQLException SQLEx) {
        logger.fatal("There was a problem with the database connection.");
        logger.fatal(SQLEx);
        logger.fatal(SQLEx.getCause());
    }
    catch (NamingException nameEx) {
        logger.fatal("There was a naming exception");
        logger.fatal(nameEx);
        logger.fatal(nameEx.getCause());
    }
}

/**
 * Execute a query. Do not use for statements (update delete insert etc).
 *
 * @return A ResultSet of the execute query. A set of size zero if no results were returned. It is never null.
 * @see #executeUpdate() for running update, insert delete etc.
 */

public ResultSet executeQuery() {
    ResultSet result = null;
    try {
        result = prepStmt.executeQuery();
        logger.debug(prepStmt.toString());
    }
    catch (SQLException SQLEx) {
        logger.fatal("There was an error running a query");
        logger.fatal(SQLEx);
    }
    return result;
}

SNIP

public void close() {
    try {
        prepStmt.close();
        prepStmt = null;

        conn.close();
        conn = null;
    } catch (SQLException SQLEx) {
        logger.warn("There was an error closing the database connection.");
    }
}
}

这是在使用数据库连接的javabean中。

public LinkedList<ImportantNoticeBean> getImportantNotices() {

    DatabaseBean noticesDBBean = new DatabaseBean();
    LinkedList<ImportantNoticeBean> listOfNotices = new LinkedList<ImportantNoticeBean>();

    try {
        PreparedStatement preStmt = noticesDBBean.getConn().prepareStatement("SELECT pseudonym, message, date_to, date_from " +
                "FROM importantnotices, users " +
                "WHERE importantnotices.username = users.username " +
                "AND NOW() >= date_from AND NOW() <= date_to;");

        noticesDBBean.setPrepStmt(preStmt);
        ResultSet result = noticesDBBean.executeQuery();

        while (result.next()) {
            ImportantNoticeBean noticeBean = new ImportantNoticeBean();

            noticeBean.setAuthor(result.getString("pseudonym"));
            noticeBean.setMessage(result.getString("message"));
            noticeBean.setDateTo(result.getDate("date_to"));
            noticeBean.setDateFrom(result.getDate("date_from"));

            listOfNotices.add(noticeBean);
        }

        result.close();

    } catch (SQLException SQLEx) {
        logger.error("There was an error in ImportantNoticesBean.getImportantNotices()");
        logger.error(SQLEx);
    } finally {
        noticesDBBean.close();
    }
    return listOfNotices;
}

<Context reloadable="true">

    <Resource name="jdbc/mysite"
              auth="Container"
              type="javax.sql.DataSource"
              username="user"
              password="password"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/mysite"
              maxActive="10"
              maxIdle="5"
              maxWait="6000"
              removeAbandoned="true"
              logAbandoned="false"
              removeAbandonedTimeout="20"
            />
</Context>

6 个答案:

答案 0 :(得分:2)

您似乎正在正确关闭连接 - 除了prepStmt.close()抛出SQLException的情况,我找不到连接泄漏。

您使用的是哪种池实施?当您关闭连接时,池不需要立即关闭基础MySQL连接 - 毕竟这是连接池的重点!所以从MySQL方面来说,虽然你的应用程序没有使用任何连接,但连接看起来仍然存在;它们可能只是由TC连接池保存。

您可能想要尝试连接池的设置。请求它在系统空闲时缩小池。或者,要求它定期刷新所有连接。或者,它对从MySQL等获得的并发连接数有严格的上限。

检查代码是否存在连接泄漏的一种方法是强制ds.getConnection()始终打开新的物理连接,并强制conn.close()释放连接(如果连接池具有这些连接的设置) 。然后,如果您在MySQL端观察连接,您可能能够确定代码是否确实存在连接泄漏。

答案 1 :(得分:2)

这是一个类似的问题 - Connection Pool Settings for Tomcat

这是我对这个问题的回答,它解决了另一个人的问题。它也可以帮助你。

Tomcat Documentation

DBCP使用Jakarta-Commons数据库连接池。它依赖于Jakarta-Commons组件的数量:

* Jakarta-Commons DBCP
* Jakarta-Commons Collections
* Jakarta-Commons Pool

我正在使用相同的连接池,我正在设置这些属性以防止同样的事情,它只是没有通过tomcat配置。 但如果第一件事不起作用,请试试这些。

testWhileIdle=true
timeBetweenEvictionRunsMillis=300000

答案 2 :(得分:2)

好的,我可能会对此进行排序。我已将数据库配置资源更改为以下内容:

*SNIP*
maxActive="10"
maxIdle="5"
maxWait="7000"
removeAbandoned="true"
logAbandoned="false"
removeAbandonedTimeout="3"
*SNIP*

现在效果还不错。发生了什么,afaik,是一旦我到达十个连接,那么Tomcat正在检查被放弃的连接(空闲时间> 3)。每次达到最大连接时,它都会在批处理作业中执行此操作。这个问题的潜在问题是,如果我需要同时运行10个以上的查询(对我来说不是唯一的)。重要的是removeAbandonedTimeout小于maxWait。

这应该发生什么?即这是游泳池应该运作的方式吗?如果看起来,至少在我看来,你会等到某事(连接)在修复之前被打破而不是在第一时间让它“破裂”。也许我还没有得到它。

答案 3 :(得分:1)

  

我们发现连接未正确关闭并且处于“睡眠”模式

这实际上只有一半。

我遇到的问题实际上是每个应用程序都在定义与数据库服务器的新连接。因此,每次我关闭所有连接时,App A会根据它的WEB.xml配置文件创建一堆新连接并快乐地运行。应用程序B也会这样做。问题是它们是独立池,试图获取服务器定义的限制。我猜这是一种竞争条件。因此,当App A完成连接时,它等待再次使用它们,直到超时已经过去,而现在需要连接的App B被拒绝资源,即使App A已完成并且应该回到池中。超时过后,连接被释放,B(或C等)可以再次获取。

e.g。如果限制为10(mySQL配置文件限制)并且每个应用程序已配置为使用最多10个,则将有20次连接尝试。显然这是一个糟糕的情况。

解决方案是RTFM并放置connection details in the right place。这确实使共享发布变得很痛苦,但是有很多方法(例如从上下文链接到其他xml文件)。

只是为了明确:我在WEB.xml中为每个应用程序添加了连接细节,并且对此进行了争论。

答案 4 :(得分:0)

@binil错过了一件事,在异常情况下你没有关闭结果集。根据驱动程序的实现,这可能会导致连接保持打开状态。将result.close()调用移动到finally块。

答案 5 :(得分:0)

我使用的是与您相同的配置。如果mysql管理员(windows)中的连接显示它处于睡眠模式,则仅表示已汇总但未使用。我检查了这个运行一个测试程序程序,多个线程对Mysql进行随机查询。如果它有帮助,这是我的配置:

        defaultAutoCommit="false"
        defaultTransactionIsolation="REPEATABLE_READ"
        auth="Container"
        type="javax.sql.DataSource"
        logAbandoned="true" 
          removeAbandoned="true"
        removeAbandonedTimeout="300" 
        maxActive="-1"
        initialSize="15"
        maxIdle="10"
        maxWait="10000" 
        username="youruser"
        password="youruserpassword"
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://yourhost/yourdatabase"/>