【发布时间】:2015-08-27 18:49:49
【问题描述】:
这是与 Grails 2.4.4 一起使用的。我遇到了似乎与 Grails 事务管理有关的连接池泄漏。有人知道问题出在哪里吗?
来自 Tomcat JDBC 池的可疑泄漏检测消息:
015-06-11 13:44:03,483 [PoolCleaner[2120388162:1434055276957]] [||] WARN pool.ConnectionPool - Connection has been marked suspect, possibly abandoned PooledConnection[org.postgresql.jdbc4.Jdbc4Connection@4fcf9353][89657 ms.]:java.lang.Exception
at org.apache.tomcat.jdbc.pool.ConnectionPool.getThreadDump(ConnectionPool.java:1063)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:780)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:619)
at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:188)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:128)
at org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy$LazyConnectionInvocationHandler.getTargetConnection(LazyConnectionDataSourceProxy.java:403)
at org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy$LazyConnectionInvocationHandler.invoke(LazyConnectionDataSourceProxy.java:376)
at com.sun.proxy.$Proxy77.prepareStatement(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy$TransactionAwareInvocationHandler.invoke(TransactionAwareDataSourceProxy.java:240)
at com.sun.proxy.$Proxy77.prepareStatement(Unknown Source)
at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:4512)
at groovy.sql.Sql$CreatePreparedStatementCommand.execute(Sql.java:4491)
at groovy.sql.Sql.getAbstractStatement(Sql.java:4342)
at groovy.sql.Sql.getPreparedStatement(Sql.java:4357)
at groovy.sql.Sql.getPreparedStatement(Sql.java:4434)
at groovy.sql.Sql.access$900(Sql.java:228)
at groovy.sql.Sql$PreparedQueryCommand.runQuery(Sql.java:4622)
at groovy.sql.Sql$AbstractQueryCommand.execute(Sql.java:4553)
at groovy.sql.Sql.rows(Sql.java:1954)
at groovy.sql.Sql.firstRow(Sql.java:2192)
at groovy.sql.Sql$firstRow.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:120)
...
在 resources.groovy 中:
import groovy.sql.Sql
beans = {
sql(Sql, ref("dataSource"));
}
在 DataSource.groovy 中:
dataSource {
url = "jdbc:postgresql:testdb"
username = "USERNAME"
password = "PASSWORD"
driverClassName = "org.postgresql.Driver"
dialect = "org.hibernate.dialect.PostgreSQL9Dialect"
pooled = true
jmxEnabled = true
readOnly = false
autoCommit = false
properties {
initialSize = 1
maxActive = 15
minIdle = 1
maxIdle = 2
maxWait = 20000 // 20 seconds
maxAge = 20 * 60000 // 20 minutes
timeBetweenEvictionRunsMillis = 15000
minEvictableIdleTimeMillis = 10000
validationQueryTimeout = 10 // 10 seconds
validationInterval = 15000 // 15 seconds
testOnBorrow = true
testWhileIdle = true
testOnReturn = false
logValidationErrors = true
removeAbandonedTimeout = 60 * 1440 // 24 hours, use suspectTimeout instead
removeAbandoned = true // needed for suspectTimeout
suspectTimeout = 60
logAbandoned = true // needed for suspectTimeout logging
validationQuery = "SELECT 1"
}
}
TestController.groovy:
class TestController {
def testService
def test() {
Integer id = testService.test()
render "Testing: $id\n"
}
}
TestService.groovy:
import grails.transaction.Transactional
@Transactional
class TestService {
def sql
@Transactional
Integer test() {
def row = sql.firstRow("select id from TestTable where name = 'Test'")
return row.id
}
}
如果查询从事务服务移动到控制器,泄漏检测消息不会显示,我认为这是因为控制器不是事务性的。
如果我将 resources.groovy 更改为使用未代理版本的数据源,它也不会报告泄漏消息:
import groovy.sql.Sql
beans = {
sql(Sql, ref("dataSourceUnproxied"));
}
深入研究实际的 groovy.sql.Sql 代码,在我看来问题与 firstRow() 使用 try {} finally{} 块从池中打开和关闭自己的连接这一事实有关。但是它获得的连接是通过 Spring TransactionAwareConnectionFactoryProxy 代理的。
在代理源代码中,我看到了:
else if (method.getName().equals("close")) {
// Handle close method: only close if not within a transaction.
ConnectionFactoryUtils.doReleaseConnection(this.target, this.connectionFactory);
return null;
}
好的,如果在事务中,底层连接不会释放回池中吗?这对我来说没有多大意义,因为它总是在 @Transactional 服务方法中的事务中,所以连接永远不会关闭,除非 Grails 或 Spring 中的其他地方有代码在提交后关闭()连接()?
【问题讨论】:
-
你不需要在类的顶部声明它两次,如果它用于类内的所有内容 - 或者如果你的服务将成为混合服务,则在每次调用时进一步声明非事务性和事务性的。关于您的实际问题,很难说github.com/vahidhedayati/documentation/blob/master/grails/… 看看这里,也许您可以添加 REQUIRES_NEW 看看它是否有任何区别。为了更好地理解 Grails 事务,请观看此youtube.com/watch?v=JNey9T--rLE
-
将传播更改为 REQUIRES_NEW 似乎确实可以防止泄漏检测消息,但我肯定想了解原因,所以我知道应该在我的代码中在哪里使用它。 Grails 中的默认设置似乎是必需的。这是否意味着我应该将默认传播行为更改为 REQUIRES_NEW 应用程序范围,或者我应该只在我的服务入口方法(即控制器调用的方法)上使用 @Transactional(propagation=Propagation.REQUIRES_NEW) 并依赖REQUIRED 其他方法的默认值?
-
我发现了一些奇怪的东西,为什么你将类和方法都注释为事务性的?尝试其中一种,而不是两者兼而有之。不确定这是否重要(很长时间没有查看 @transactional 的 AST)。
-
另外,您知道您正在创建一个在应用程序范围内共享的 sql 实例,对吗?在我看来,这件事有些不对劲。如果不深入研究实现并确保它理解从池中获取新连接并将其返回到池中,我会犹豫是否使用这种方法。据我所知,sql 是有状态的,并且它的多个并发使用将是拼写灾难......
-
@JoshuaMoore 是的,我也考虑过这一点,应该将其作为我的问题的一部分。我被告知可以将 Sql 实例创建为 Spring bean 并以这种方式重用,但我被告知的内容可能不正确。这是一篇博文,也展示了如何将其作为 Spring bean:mrhaki.blogspot.com/2014/03/…。