JDBC Instrumentation和ORA-01000:最大打开游标超过

时间:2016-10-12 22:03:38

标签: java oracle jdbc oracle11g

我正在尝试更好地检测当连接创建关闭时,哪些Web应用程序在我们的Tomcat JDBC连接池中使用Oracle(11g)连接;这样,我们可以通过监视V$SESSION表来查看哪些应用程序正在使用连接。这是有效的,但是由于添加了这个“检测”,我看到ORA-01000: maximum open cursors exceeded错误被记录,并注意到在加载测试期间某些连接被从池中删除(这可能没问题,因为我启用了testOnBorrow,所以我假设连接被标记为无效并从池中删除。)

我花了大部分时间在网上搜索可能的答案。这是我尝试过的(一段时间后都会导致打开游标错误)...

以下方法都以同样的方式被称为......

创建

  1. 我们从池中获取连接
  2. 我们调用一个执行以下代码的方法,传入Web应用程序的上下文名称
  3. 关闭

    1. 我们已关闭连接(返回池中)
    2. 在我们对连接发出close()之前,我们调用一个执行下面代码的方法,将“Idle”作为要存储在V$SESSION
    3. 中的名称传递

      方法1:

      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();
      }
      

      方法2:

      我升级到12c OJDBC驱动程序(ojdbc7)并在连接上使用了原生的setClientInfo方法......

      // requires ojdbc7.jar and oraclepki.jar to work (setEndToEndMetrics is deprecated in ojdbc7)
      connection.setClientInfo("OCSID.CLIENTID", appId);
      

      方法3:

      我目前正在使用这种方法。

      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);
      

      方法4:

      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:超出最大开放游标数),但我仍然遗漏了一些东西。任何帮助将不胜感激。

1 个答案:

答案 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 DESCRORA-01000 open cursors时,没有返回可靠的点击数(返回的大多数点击是为了确保你关闭了你的连接资源,即,ResultSet s,Statement s等。我认为这表明通过JDBC对Oracle的数据库元数据查询是ORA-01000错误的罪魁祸首。我希望这对其他人有用。感谢。