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

发布于 2024-08-27 12:45:22 字数 1467 浏览 4 评论 0原文

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

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

Note: we reuse single connection.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

荒岛晴空 2024-09-03 12:45:22

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

因此,当不再需要 JDBC 资源时,必须关闭它们,特别是 java.sql.ResultSet 和 java.sql.Statement。如果它们没有关闭,应用程序就会发生资源泄漏。

在重用 Connection 对象的情况下,您必须注意这样一个事实:只要连接存在且事务尚未结束,打开的 Oracle 游标就会保持打开状态并处于使用状态。当应用程序提交时,打开的游标将被释放。

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

困难在于oracle的内部参数视图(v$open_cursor、v$sesstat等)无法显示打开的游标(可重用)和打开的游标(仍然被阻止(不可重用!))之间的区别。未封闭的结果集或语句。如果您关闭finally 块中的所有Statement 和ResultSet 对象,您的应用程序就完全没问题。

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

ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;

The init.ora parameter open_cursors defines the maximum of opened cursors a session can have at once. It has a default value of 50. If the application exceeds this number the error "ORA-01000: maximum open cursors exceeded" is raised.

Therefore it's mandatory to close the JDBC resources when they are not needed any longer, in particular java.sql.ResultSet and java.sql.Statement. If they are not closed, the application has a resource leak.

In case of reusing the Connection object, you must be aware of the fact that the opened oracle cursors are kept open and in use as long the connection exists and the transaction has not ended. When the application commits, the opened cursors are released.

Therefore as an application designer you need to know a rough estimation of the needed open cursors for your most complex transaction.

The difficulty lies in the inability of oracle's internal parameter views (v$open_cursor, v$sesstat, et. al.) to show the difference between opened cursors, which are reusable and opened cursors, which are still blocked (not reusable!) by an unclosed ResulSet or Statement. If you close all Statement and ResultSet objects in your finally block, your application is perfectly fine.

Adjusting the init.ora parameter works like this (our application needs 800 cursors at a maximum)

ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;
笨笨の傻瓜 2024-09-03 12:45:22

正确的方法是在自己的 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);
}

The correct way to do it is to close every resource in a finally block in its own try/catch block. I usually use a static utility class like this:

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
}

So I'd write your code like this:

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);
}
吐个泡泡 2024-09-03 12:45:22

通常,您会将 ResultSet 和 Statement 的关闭语句放入 finally 块中,以确保即使发生异常(可能是您在这里遇到的问题),它们也会被调用。在当前代码中,如果发生 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;

Normally you would put the close statements for your ResultSet and Statement into a finally block to ensure that they are called even if an exception occurs (could be the issue you are having here). In your current code, if a SQLException occurs then the two close( ) method calls will never occur and cursors would be left open.

Also what query are you using in Oracle to see the count of open cursors?

Edit:
That code should be closing the cursor. If it isn't then you should be able to see a 1 to 1 correlation of calling your method and the cursor count going up by 1. Be sure there isn't some unexpected process that is causing the cursor count to go up.

If you have the privileges, you can run this query against the database to see the open cursor count by sid to see if maybe it is some other process that is increasing the cursors and not yours specifically. It will pull back any with more than 10 cursors open, you can raise this to filter out the noise or narrow it specifically by username or 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;

Another query that may be helpful, in case multiple sid's are using the same query string so the above does not reveal the offender well:

 select oc.sql_text, count(*) 
   from v$open_cursor oc 
   group by oc.sql_text 
   having count(*) > 10 
   order by count(*) desc;
草莓味的萝莉 2024-09-03 12:45:22

我刚刚遇到了同样的问题,并发现 - 如果您不关闭连接(因为您稍后可能会重用它)-您至少必须执行 connection.rollback()connection.commit() 释放打开的游标并关闭 ResultSet 和声明。

I just had the same problem and found that - if you do not close the connection (because you will maybe reuse it later on) - you at least have to do a connection.rollback() or connection.commit() to free the open cursors togehther with closing the ResultSet and Statements.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文