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

时间:2010-04-01 12:43:27

标签: java oracle jdbc cursor resultset

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

************************************************
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<IngisObject> objects = new Vector<IngisObject>();
    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();
    }

4 个答案:

答案 0 :(得分:24)

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;

答案 1 :(得分:7)

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

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

修改
该代码应该关闭光标。如果不是那么你应该能够看到调用方法和光标计数增加1的1对1相关性。确保没有一些意外的进程导致光标计数增加。< / p>

如果您拥有这些权限,则可以对数据库运行此查询,以通过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;

答案 2 :(得分:6)

正确的方法是在自己的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<IngisObject> objects = new Vector<IngisObject>();

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

答案 3 :(得分:6)

我遇到了同样的问题并发现 - 如果你不关闭连接(因为你以后可能会重复使用它) - 你至少要做connection.rollback()connection.commit()释放打开的游标以关闭ResultSet和Statements。