关于数据库:Java ConnectionPool连接未关闭,停留在“睡眠”状态

关于数据库:Java ConnectionPool连接未关闭,停留在“睡眠”状态

Java ConnectionPool connection not closing, stuck in 'sleep'

我有一个Web应用程序,它使用JNDI查找来获得与数据库的连接。

连接工作正常,并返回查询没有问题。 向我们发出的问题是,连接无法正确关闭,并停留在"睡眠"模式下(根据mysql管理员)。 这意味着它们变得无法使用,然后我用尽了所有连接。

有人可以给我一些关于如何使连接成功返回池的指示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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

1
2
3
4
5
6
7
8
9
10
11
12
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中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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>

好吧,我可能已经排序了。我已将数据库配置资源更改为以下内容:

1
2
3
4
5
6
7
8
*SNIP*
maxActive="10"
maxIdle="5"
maxWait="7000"
removeAbandoned="true"
logAbandoned="false"
removeAbandonedTimeout="3"
*SNIP*

目前,这已经足够好了。 afaik,正在发生的事情是,一旦我到达十个连接,Tomcat就会检查废弃的连接(空闲时间> 3)。每次达到最大连接数时,它将以批处理方式执行此操作。潜在的问题是,如果我需要同时运行10个以上的查询(不是我独有的)。重要的是removeAbandonedTimeout小于maxWait。

这是应该发生的事吗?即池应该以这种方式运行吗?如果看来,至少在我看来,您将等到某个东西(连接)断开之后再进行修复,而不是首先不要让它"断开"。也许我还是不明白。


这是一个类似的问题-Tomcat的连接池设置

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

Tomcat文档

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

1
2
3
* Jakarta-Commons DBCP
* Jakarta-Commons Collections
* Jakarta-Commons Pool

我正在使用相同的连接池内容,并且正在设置这些属性以防止未通过tomcat配置该内容。
但是,如果第一件事不起作用,请尝试这些。

1
2
testWhileIdle=true
timeBetweenEvictionRunsMillis=300000

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

您正在使用什么池实现?关闭连接时,该池不必立即关闭基础的MySQL连接-毕竟这是连接池的关键!因此,从MySQL的角度来看,尽管您的应用程序未使用任何连接,但连接看起来仍然很活跃。它们可能只是由TC连接池持有。

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

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


The issue us that the connection does not close properly and is stuck in the 'sleep' mode

这实际上只有一半。

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

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

解决方案是使用RTFM,并将连接详细信息放在正确的位置。这确实使共享发布很麻烦,但是有很多解决方法(例如,从上下文链接到其他xml文件)。

明确地说:我将每个应用程序的连接详细信息放在WEB.xml中,并为此进行了斗争。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        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"/>

@binil遗漏的一件事是,在发生异常的情况下,您不会关闭结果集。根据驱动程序的实现,这可能会导致连接保持打开状态。将result.close()调用移至finally块。


推荐阅读