在MySQL、Grails 2应用中,保持池化连接活动的正确方法(或在较长时间不活动时将其超时并获取新连接)

37
我有一个Grails应用程序,活动会突然变得非常高,但是通常会有数小时到整夜的不活动期。我注意到早上的第一批用户会遇到以下类型的异常,我认为这是由于连接池中的连接变得过时并且MySQL数据库关闭了它们导致的。
在Google搜索中,我发现了关于是否使用Connector/J连接属性“autoReconnect=true”的矛盾信息(以及即使连接被恢复,客户端是否仍会收到异常),或者设置其他属性以定期驱逐或刷新空闲连接,借用测试等。 Grails在DBCP下面使用。 我目前拥有如下简单的配置,并正在寻找如何最好地确保从长时间不活动期后抓取的任何连接都是有效且未关闭的答案。
dataSource {
        pooled = true
        dbCreate = "update"
        url = "jdbc:mysql://my.ip.address:3306/databasename"
        driverClassName = "com.mysql.jdbc.Driver"
        dialect = org.hibernate.dialect.MySQL5InnoDBDialect
        username = "****"
        password = "****"
        properties {
          //what should I add here?
          }
    }

异常

    2012-06-20 08:40:55,150 [http-bio-8443-exec-1] ERROR transaction.JDBCTransaction  - JDBC begin failed
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 64,129,968 milliseconds ago.  The last packet sent successfully to the server was 64,129,968 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1116)
    at com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3851)
    ...... Lots more .......
Caused by: java.sql.SQLException: Already closed.
    at org.apache.commons.dbcp.PoolableConnection.close(PoolableConnection.java:114)
2个回答

37

最简单的方法是配置连接池,以指定在将连接传递给应用程序之前要运行的查询来测试连接:

validationQuery="select 1 as dbcp_connection_test"
testOnBorrow=true

这个“连接验证”查询可以在其他事件上运行。我不确定这些事件的默认设置:

testOnReturn=true
testWhileIdle=true

还有一些配置设置可以限制连接池中空闲连接的 "年龄",如果空闲连接在服务器端被关闭,则这些设置会很有用。

minEvictableIdleTimeMillis
timeBetweenEvictionRunsMillis

http://commons.apache.org/dbcp/configuration.html


谢谢你提供的链接,我在直接查看Java文档时不小心错过了那个页面。我会结合那些实现一下,看看效果如何。 - Peter
不幸的是,DBCP仍然没有配置来限制连接的年龄或连接被使用的次数,在从池中驱逐之前。(其他连接池实现有这个功能。) - spencer7593
虽然,Spencer7593,如果连接正在使用或因在activity处于不良状态而被清除/刷新,则这不应该成为问题,我认为,因为它不会影响用户。 - Peter
@Pete:这对我们很重要,因为在Oracle服务器连接中存在游标泄漏问题,其中validationQuery可以正常运行,但更复杂的语句会抛出异常(超出游标限制),该问题仅发生在已被重用许多次的连接上,而在“新”连接上从未发生过。通过不同的连接池实现限制使用次数是解决该问题的一种方法。(我们的问题与空闲时间无关。) - spencer7593
1
@spencer7593 最新消息:DBCP 2.0+(于2014年发布)支持maxConnLifetimeMillis属性,正是你在2012年所缺少的。虽然花费了一些时间,但他们终于做到了... - Attila Csipak

9

我不确定这是否是处理数据库连接的最佳方式,但我遇到了与您描述的相同的问题。我尝试了很多方法,最终使用了c3p0连接池

使用c3p0,您可以强制应用程序在一定时间后刷新数据库连接。

c3p0.jar放入您的lib文件夹,并将配置添加到conf/spring/resources.groovy中。

我的resources.groovy看起来像这样:

import com.mchange.v2.c3p0.ComboPooledDataSource
import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH

beans = {
    /**
    * c3P0 pooled data source that forces renewal of DB connections of certain age
    * to prevent stale/closed DB connections and evicts excess idle connections
    * Still using the JDBC configuration settings from DataSource.groovy
    * to have easy environment specific setup available
    */
    dataSource(ComboPooledDataSource) { bean ->
        bean.destroyMethod = 'close'
        //use grails' datasource configuration for connection user, password, driver and JDBC url
        user = CH.config.dataSource.username
        password = CH.config.dataSource.password
        driverClass = CH.config.dataSource.driverClassName
        jdbcUrl = CH.config.dataSource.url
        //force connections to renew after 4 hours
        maxConnectionAge = 4 * 60 * 60
        //get rid too many of idle connections after 30 minutes
        maxIdleTimeExcessConnections = 30 * 60
    }
 }  

一些连接池实现比DBCP提供更强大的功能。尽管DBCP的较新版本在“最大空闲时间”到期时支持逐出,但DBCP仍缺乏其他配置选项,例如最大年龄和最大使用次数。 - spencer7593
1
我发现在Grails堆栈上遇到连接超时问题时,C3PO的表现非常出色。 - Visionary Software Solutions

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接