web-dev-qa-db-ja.com

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

Init.oraパラメータopen_cursorsは、セッションが一度に保持できる開いているカーソルの最大数を定義します。デフォルト値は50です。アプリケーションがこの数を超えると、エラー「ORA-01000:最大オープンカーソル数を超えました」が発生します。

したがって、不要になったJDBCリソース、特にJava.sql.ResultSetとJava.sql.Statementは、閉じる必要があります。それらが閉じられていない場合、アプリケーションにリソースリークがあります。

Connectionオブジェクトを再利用する場合、開いているOracleカーソルは開いたままであり、接続が存在する限り使用されていることに注意する必要がありますandトランザクションは終了していません。アプリケーションがコミットすると、開いているカーソルが解放されます。

したがって、アプリケーション設計者は、最も複雑なトランザクションに必要なオープンカーソルの概算を知る必要があります。

問題は、Oracleの内部パラメータービュー(v $ open_cursor、v $ sesstatなど)が、再利用可能な開いているカーソルと、ブロックされている(再利用できない!)カーソルの違いを表示できないことです。閉じられていないResulSetまたはステートメント。最終ブロックのすべてのStatementオブジェクトとResultSetオブジェクトを閉じると、アプリケーションは完全に正常になります。

Init.oraパラメータの調整はこのように機能します(このアプリケーションでは最大800カーソルが必要です)

ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;
24
Oliver Michels

通常、ResultSetとStatementのcloseステートメントをfinallyブロックに入れて、例外が発生した場合でも呼び出されるようにします(問題が発生している可能性があります)。現在のコードでは、SQLExceptionが発生した場合、2つの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;
7
Doug Porter

これを行う正しい方法は、独自の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);
}
6
duffymo

私は同じ問題を抱えていて、あなたが接続を閉じない場合(後で再利用する可能性があるため)-少なくとも connection.rollback() または connection.commit() を実行して、ResultSetとステートメントを閉じると同時に、開いているカーソルを解放します。

6
FrVaBe