【发布时间】:2017-02-21 21:02:51
【问题描述】:
当连接创建和关闭时,我正在尝试更好地检测哪些 Web 应用程序在我们的 Tomcat JDBC 连接池中使用 Oracle (11g) 连接;这样,我们可以通过监视V$SESSION 表来查看哪些应用程序正在使用连接。这是有效的,但是自从添加了这个“仪器”后,我看到ORA-01000: maximum open cursors exceeded 错误被记录并注意到在负载测试期间一些连接被从池中删除(这可能很好,因为我启用了testOnBorrow,所以我假设连接被标记为无效并从池中删除)。
我花了一周的大部分时间在互联网上搜寻可能的答案。这是我尝试过的(一段时间后都导致打开游标错误)...
以下方法的调用方式都一样...
创建时
- 我们从池中获得一个连接
- 我们调用一个方法来执行以下代码,并传入 Web 应用程序的上下文名称
关闭时
- 我们已关闭连接(返回池)
- 在连接上发出
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 方法(或来自DBMS_APPLICATION_INFO.SET_MODULE proc,或来自isValid() 电话)。我试图在方法 1 和 4 中明确地创建和关闭 Statement (CallableStatement) 和 ResultSet 对象,但它们没有区别。
我不想增加允许的游标数量,因为这只会延迟不可避免的情况(在我添加“仪器”之前,我们从未遇到过这个问题)。
我已经阅读了出色的帖子 here(java.sql.SQLException: - ORA-01000: 超出了最大打开游标),但我仍然必须遗漏一些东西。任何帮助将不胜感激。
【问题讨论】:
-
你怎么知道错误与仪器有关?没有对任何其他代码进行任何更改吗?
v$session为持有打开游标的会话显示什么 - 是为这些会话记录的 appId,还是来自其他地方?该查询看起来像是在获取虚假元数据;您是否还有其他代码可以查询真实元数据(来自*_tab_columns),可能来自您的 Tomcat 环境之外的脚本或客户端? -
未对其他代码进行任何更改。我正在使用目前在 prod 中仅针对两个 Web 应用程序的主干代码进行负载测试,因此我知道它是稳定的并且不会在 Prod 中导致此问题(我在测试时没有访问其他应用程序,并且我在每次测试之前重新启动 Tomcat) .此代码位于一个通用 DAO 类中,由 Tomcat 中运行的所有应用程序共享。池使用单个帐户,我可以在
V$SESSION中看到appId名称 - 我记录了连接的 SID(在池中),并且在查看打开的游标时可以在V$SESSION中关联它。没有外部脚本正在使用池中使用的帐户。 -
打开游标查询:
select sid, sql_text, count(*) as "OPEN CURSORS", USER_NAME from v$open_cursor where user_name='<MY_POOL_ID>' group by sid, sql_text, user_name order by "OPEN CURSORS" DESC
标签: java oracle jdbc oracle11g