我正在尝试更好地检测当连接创建和关闭时,哪些Web应用程序在我们的Tomcat JDBC连接池中使用Oracle(11g)连接;这样,我们可以通过监视V$SESSION
表来查看哪些应用程序正在使用连接。这是有效的,但是由于添加了这个“检测”,我看到ORA-01000: maximum open cursors exceeded
错误被记录,并注意到在加载测试期间某些连接被从池中删除(这可能没问题,因为我启用了testOnBorrow
,所以我假设连接被标记为无效并从池中删除。)
我花了大部分时间在网上搜索可能的答案。这是我尝试过的(一段时间后都会导致打开游标错误)...
以下方法都以同样的方式被称为......
close()
之前,我们调用一个执行下面代码的方法,将“Idle”作为要存储在V$SESSION
CallableStatement cs = connection.prepareCall("{call DBMS_APPLICATION_INFO.SET_MODULE(?,?)}");
try {
cs.setString(1, appId);
cs.setNull(2, Types.VARCHAR);
cs.execute();
log.trace(">>> Executed Oracle DBMS_APPLICATION_INFO.SET_MODULE with module_name of '" + appId + "'");
} catch (SQLException sqle) {
log.error("Error trying to call DBMS_APPLICATION_INFO.SET_MODULE('" + appId + "')", sqle);
} finally {
cs.close();
}
我升级到12c OJDBC驱动程序(ojdbc7)并在连接上使用了原生的setClientInfo
方法......
// requires ojdbc7.jar and oraclepki.jar to work (setEndToEndMetrics is deprecated in ojdbc7)
connection.setClientInfo("OCSID.CLIENTID", appId);
我目前正在使用这种方法。
String[] app_instrumentation = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
app_instrumentation[OracleConnection.END_TO_END_CLIENTID_INDEX] = appId;
connection.unwrap(OracleConnection.class).setEndToEndMetrics(app_instrumentation, (short)0);
// in order for this to be sent, a query needs to be sent to the database - this works fine when a
// connection is created, but when it is closed, we need a little something to get the change into the db
// try using isValid()
connection.isValid(1);
String[] app_instrumentation = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
app_instrumentation[OracleConnection.END_TO_END_CLIENTID_INDEX] = appId;
connection.unwrap(OracleConnection.class).setEndToEndMetrics(app_instrumentation, (short)0);
// in order for this to be sent, a query needs to be sent to the database - this works fine when a
// connection is created, but when it is closed, we need a little something to get the change into the db
if ("Idle".equalsIgnoreCase(appId)) {
Statement stmt = null;
ResultSet rs = null;
try {
stmt = connection.createStatement();
rs = stmt.executeQuery("select 1 from dual");
} finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
}
}
当我查询打开的游标时,我注意到池中使用的帐户返回了以下SQL(对于池中的每个连接)...
select NULL NAME, -1 MAX_LEN, NULL DEFAULT_VALUE, NULL DESCR
这在我们的代码中的任何地方都没有显式存在,因此我只能假设它在运行验证查询(select 1 from dual
)时或来自setEndToEndMetrics
方法(或来自{{ {1}} proc,或来自DBMS_APPLICATION_INFO.SET_MODULE
电话)。我试图在方法1和方法4中明确创建和关闭isValid()
(Statement
)和CallableStatement
个对象,但它们没有区别。
我不想增加允许游标的数量,因为这只会延迟不可避免的游标(我们在“仪器”中添加之前从未遇到过这个问题)。
我已经阅读了优秀帖子here( java.sql.SQLException: - ORA-01000:超出最大开放游标数),但我仍然遗漏了一些东西。任何帮助将不胜感激。
答案 0 :(得分:2)
所以Poole先生的声明:“那个查询看起来像是假冒元数据”在我的脑海里响起了一个钟声。
我开始怀疑是否在池的数据源的testOnBorrow
属性上运行了验证查询的一些未知残余(即使验证查询定义为select 1 from dual
)。我从配置中删除了它,但它没有效果。
然后我尝试删除在V$SESSION
中设置客户端信息的代码(上面的方法3); Oracle继续显示异常查询,仅在几分钟后,会话就会达到最大开放游标限制。
然后我发现在我们的DAO类中有一个“logging”方法,它记录了连接对象的一些元数据(当前自动提交,当前事务隔离级别,JDBC驱动程序版本等设置的值)。在此日志记录中,getClientInfoProperties()
对象使用DatabaseMetaData
方法。当我查看JavaDocs这个方法的时候,那个不寻常的查询来自哪里变得非常清楚;检查出来......
ResultSet java.sql.DatabaseMetaData.getClientInfoProperties() throws SQLException
Retrieves a list of the client info properties that the driver supports. The result set contains the following columns
1. NAME String=> The name of the client info property
2. MAX_LEN int=> The maximum length of the value for the property
3. DEFAULT_VALUE String=> The default value of the property
4. DESCRIPTION String=> A description of the property. This will typically contain information as to where this property is stored in the database.
The ResultSet is sorted by the NAME column
Returns:
A ResultSet object; each row is a supported client info property
您可以清楚地看到异常查询(select NULL NAME, -1 MAX_LEN, NULL DEFAULT_VALUE, NULL DESCR
)与JavaDocs对DatabaseMetaData.getClientInfoProperties()
方法的说法相符。哇,对吧!?
这是执行该功能的代码。据我所知,从“ResultSet
”的角度来看,它看起来是正确的 - 不确定发生什么会使ResultSet
保持开放 - 它明显在finally
关闭阻止。
log.debug(">>>>>> DatabaseMetaData Client Info Properties (jdbc driver)...");
ResultSet rsDmd = null;
try {
boolean hasResults = false;
rsDmd = dmd.getClientInfoProperties();
while (rsDmd.next()) {
hasResults = true;
log.debug(">>>>>>>>> NAME = '" + rsDmd.getString("NAME") + "'; DEFAULT_VALUE = '" + rsDmd.getString("DEFAULT_VALUE") + "'; DESCRIPTION = '" + rsDmd.getString("DESCRIPTION") + "'");
}
if (!hasResults) {
log.debug(">>>>>>>>> DatabaseMetaData Client Info Properties was empty (nothing returned by jdbc driver)");
}
} catch (SQLException sqleDmd) {
log.warn("DatabaseMetaData Client Info Properties (jdbc driver) not supported or no access to system tables under current id");
} finally {
if (rsDmd != null) {
rsDmd.close();
}
}
查看日志,当使用Oracle连接时,会记录>>>>>>>>> DatabaseMetaData Client Info Properties was empty (nothing returned by jdbc driver)
行,因此未抛出异常,但也未返回任何记录。我只能假设ojdbc6(11.2.0.xx)驱动程序不能正确支持getClientInfoProperties()
方法 - 我认为没有抛出异常是奇怪的,因为缺少查询本身FROM
关键字(例如,在TOAD中执行时不会运行)。无论如何,ResultSet
至少应该被关闭(连接本身仍然会被使用 - 可能这导致Oracle即使ResultSet
被关闭也不会释放游标)。
所以我所做的所有工作都是在一个分支中(我在对原始问题的评论中提到我在干线工作 - 我的错误 - 我在一个已经创建的分支中认为它基于主干代码并没有修改 - 我没有在这里尽职尽责),所以我检查了SVN提交历史记录,发现这个额外的日志记录功能是几周前由一位队友添加的(幸运的是它还没有被提升到主干或者更高的环境 - 请注意,此代码可以正常使用Sybase数据库。我从SVN分支的更新带来了他的代码,但我从来没有真正关注更新的内容(我的坏)。我和他谈到了这段代码对Oracle的影响,我们同意从日志记录方法中删除代码。我们还设置了一个检查,仅在我们的开发环境中记录连接元数据(他说他添加了此代码以帮助解决一些驱动程序版本和自动提交问题)。完成后,我能够在没有任何打开游标问题的情况下运行我的负载测试(成功!!!)。
无论如何,我想回答这个问题,因为当我搜索select NULL NAME, -1 MAX_LEN, NULL DEFAULT_VALUE, NULL DESCR
和ORA-01000 open cursors
时,没有返回可靠的点击数(返回的大多数点击是为了确保你关闭了你的连接资源,即,ResultSet
s,Statement
s等。我认为这表明通过JDBC对Oracle的数据库元数据查询是ORA-01000
错误的罪魁祸首。我希望这对其他人有用。感谢。