关闭结果集后,Oracle不会删除游标

注意:我们重用单一连接。

************************************************ public Connection connection() { try { if ((connection == null) || (connection.isClosed())) { if (connection!=null) log.severe("Connection was closed !"); connection = DriverManager.getConnection(jdbcURL, username, password); } } catch (SQLException e) { log.severe("can't connect: " + e.getMessage()); } return connection; } ************************************************** public IngisObject[] select(String query, String idColumnName, String[] columns) { Connection con = connection(); Vector objects = new Vector(); try { Statement stmt = con.createStatement(); String sql = query; ResultSet rs =stmt.executeQuery(sql);//oracle increases cursors count here while(rs.next()) { IngisObject o = new IngisObject("New Result"); o.setIdColumnName(idColumnName); o.setDatabase(this); for(String column: columns) o.attrs().put(column, rs.getObject(column)); objects.add(o); } rs.close();// oracle don't decrease cursor count here, while it's expected stmt.close(); } catch (SQLException ex) { System.out.println(query); ex.printStackTrace(); } 

init.ora参数open_cursors定义会话一次可以拥有的打开游标的最大值。 它的默认值为50.如果应用程序超过此数,则会引发错误“ORA-01000:超出最大打开游标数”。

因此,当不再需要JDBC资源时,必须关闭它们,特别是java.sql.ResultSet和java.sql.Statement。 如果它们未关闭,则应用程序存在资源泄漏。

在重用Connection对象的情况下,您必须知道打开的oracle游标在连接存在事务尚未结束的情况下保持打开和使用。 当应用程序提交时,释放的游标将被释放。

因此,作为应用程序设计人员,您需要了解对最复杂事务所需的开放游标的粗略估计。

困难在于oracle的内部参数视图(v $ open_cursor,v $ sesstat等等)无法显示打开的游标之间的区别,这些游标是可重用的和打开的游标,它们仍被阻止(不可重复使用!)未公开的ResulSet或Statement。 如果关闭finally块中的所有Statement和ResultSet对象,则应用程序完全正常。

调整init.ora参数的工作原理如下(我们的应用程序最多需要800个游标)

 ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH; 

正确的方法是在自己的try / catch块中关闭finally块中的每个资源。 我通常使用这样的静态实用程序类:

 public class DatabaseUtils { public static void close(Connection connection) { try { if (connection != null) { connection.close(); } } catch (SQLException e) { // log exception here. } } // similar methods for ResultSet and Statement } 

所以我写这样的代码:

 public IngisObject[] select(String query, String idColumnName, String[] columns) { Vector objects = new Vector(); Connection con = null; Statement stmt = null; ResultSet rs = null; try { connection = connection(); stmt = con.createStatement(); // This is a SQL injection attack waiting to happen; I'd recommend PreparedStatemen String sql = query; rs =stmt.executeQuery(sql);//oracle increases cursors count here while(rs.next()) { IngisObject o = new IngisObject("New Result"); o.setIdColumnName(idColumnName); o.setDatabase(this); for(String column: columns) o.attrs().put(column, rs.getObject(column)); objects.add(o); } } catch (SQLException ex) { System.out.println(query); ex.printStackTrace(); } finally { DatabaseUtils.close(rs); DatabaseUtils.close(stmt); DatabaseUtils.close(con); } 

通常,您会将ResultSet和Statement的close语句放入finally块中,以确保即使发生exception也会调用它们(可能是您在此处遇到的问题)。 在当前代码中,如果发生SQLException,则将永远不会发生两个close()方法调用,并且游标将保持打开状态。

您还在Oracle中使用什么查询来查看打开游标的数量?

编辑:
该代码应该关闭光标。 如果不是那么你应该能够看到调用方法和光标计数增加1的1对1关联。确保没有一些意外的进程导致光标计数增加。

如果您具有权限,则可以对数据库运行此查询以查看sid打开的游标计数,以查看是否可能是某些其他进程正在增加游标而不是您的游标。 如果打开超过10个光标,它会向后拉,你可以通过用户名或osuser来提升它以滤除噪音或缩小它:

 select oc.sid, count(*) numCur, s.username username, s.osuser osuser, oc.sql_text, s.program from v$open_cursor oc, v$session s where s.sid = oc.sid group by oc.sid, oc.sql_text, s.username, s.osuser, s.program having count(*) > 10 order by oc.sid; 

另一个可能有用的查询,如果多个sid使用相同的查询字符串,那么上面的内容并没有很好地揭示犯罪者:

  select oc.sql_text, count(*) from v$open_cursor oc group by oc.sql_text having count(*) > 10 order by count(*) desc; 

我只是遇到了同样的问题并发现 – 如果你没有关闭连接 (因为你可能会在以后重复使用它) – 你至少需要做一个connection.rollback()或connection.commit()以释放open关闭ResultSet和Statements的游标。