Oracle doesn't remove cursors after closing result set

72,313

Solution 1

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;

Solution 2

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;

Solution 3

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

Solution 4

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.

Share:
72,313
Vladimir
Author by

Vladimir

I am continuously thinking about making development more quick, more declarative, straightforward and more human-friendly. Currently I am working on a little great product for end-user consumers which works on PC and Mac and developed mostly using Java.

Updated on July 09, 2022

Comments

  • Vladimir
    Vladimir almost 2 years

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