123

ORA-01000 SQL 例外が発生します。そのため、それに関連するいくつかのクエリがあります。

  1. 最大オープン カーソルは JDBC 接続の数に正確に関連していますか、それとも単一の接続用に作成したステートメントおよび結果セット オブジェクトにも関連していますか? (接続のプールを使用しています)
  2. データベース内のステートメント/結果セット オブジェクトの数 (接続など) を構成する方法はありますか?
  3. シングルスレッド環境でメソッドローカルステートメント/結果セットオブジェクトの代わりにインスタンス変数ステートメント/結果セットオブジェクトを使用することをお勧めしますか?
  4. ループ内で準備済みステートメントを実行すると、この問題が発生しますか? (もちろん、sqlBatch を使用することもできました) 注: ループが終了すると、pStmt は閉じられます。

    { //method try starts  
      String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
      pStmt = obj.getConnection().prepareStatement(sql);
      pStmt.setLong(1, subscriberID);
      for (String language : additionalLangs) {
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
      }
    } //method/try ends
    
    { //finally starts
       pStmt.close()
    } //finally ends 
    
  5. conn.createStatement() と conn.prepareStatement(sql) が単一の接続オブジェクトで複数回呼び出されるとどうなりますか?

Edit1: 6. Weak/Soft 参照ステートメント オブジェクトの使用は、漏洩の防止に役立ちますか?

Edit2: 1.プロジェクトで不足しているすべての「statement.close()」を見つける方法はありますか? メモリリークではないことを理解しています。しかし、ガベージ コレクションの対象となるステートメント参照 (close() が実行されていない場所) を見つける必要がありますか? 利用可能なツールはありますか? それとも、手動で分析する必要がありますか?

私がそれを理解するのを手伝ってください。

解決

ユーザー名 -VELU の Oracle DB で開いているカーソルを見つけるには

ORACLE マシンに移動し、sysdba として sqlplus を開始します。

[oracle@db01 ~]$ sqlplus / as sysdba 

次に実行します

SELECT   A.VALUE,
    S.USERNAME,
    S.SID,
    S.SERIAL#
  FROM V$SESSTAT A,
    V$STATNAME B,
    V$SESSION S
  WHERE A.STATISTIC# = B.STATISTIC#
    AND S.SID        = A.SID
    AND B.NAME       = 'opened cursors current'
    AND USERNAME     = 'VELU';

可能であれば、私の解決策をより理解するために私の回答を読んでください

4

14 に答える 14

308

maximum-open-cursors エラーである ORA-01000 は、Oracle データベース開発において非常に一般的なエラーです。Java のコンテキストでは、アプリケーションが、データベース インスタンスに構成されているカーソルよりも多くの ResultSet を開こうとしたときに発生します。

一般的な原因は次のとおりです。

  1. 設定ミス

    • アプリケーションには、DB 上のカーソルよりも多くのスレッドがデータベースにクエリを実行しています。1 つのケースは、データベース上のカーソルの数よりも大きい接続とスレッド プールがある場合です。
    • 多くの開発者またはアプリケーションが同じ DB インスタンス (おそらく多くのスキーマを含む) に接続されており、一緒に使用する接続が多すぎます。
    • 解決:

  2. カーソルリーク

    • アプリケーションが ResultSet (JDBC の場合) またはカーソル (データベースのストアド プロシージャの場合) を閉じていない
    • 解決策: カーソル リークはバグです。DB 上のカーソルの数を増やしても、避けられない障害が発生するのを遅らせるだけです。リークは、静的コード分析JDBCまたはアプリケーション レベルのロギング、およびデータベース モニタリングを使用して見つけることができます。

バックグラウンド

このセクションでは、カーソルの背後にあるいくつかの理論と、JDBC の使用方法について説明します。背景を知る必要がない場合は、これをスキップして「リークの排除」に直接進んでください。

カーソルとは

カーソルは、クエリの状態、特にリーダーが ResultSet 内にある位置を保持するデータベース上のリソースです。各 SELECT ステートメントにはカーソルがあり、PL/SQL ストアド プロシージャは必要な数のカーソルを開いて使用できます。Orafaqでカーソルの詳細を確認できます。

通常、データベース インスタンスは、複数の異なるスキーマ、それぞれが複数のセッションを持つ多くの異なるユーザーにサービスを提供します。これを行うために、すべてのスキーマ、ユーザー、およびセッションで使用できる固定数のカーソルがあります。すべてのカーソルが開いている (使用中) 場合に、新しいカーソルを必要とする要求が来ると、その要求は ORA-010000 エラーで失敗します。

カーソル数の検索と設定

この番号は通常、インストール時に DBA によって構成されます。現在使用中のカーソルの数、最大数、および構成は、Oracle SQL Developerの管理者機能でアクセスできます。SQL から次のように設定できます。

ALTER SYSTEM SET OPEN_CURSORS=1337 SID='*' SCOPE=BOTH;

JVM 内の JDBC を DB 上のカーソルに関連付ける

以下の JDBC オブジェクトは、次のデータベースの概念と密接に結びついています。

  • JDBC接続は、データベースセッションのクライアント表現であり、データベーストランザクションを提供します。接続では、一度に 1 つのトランザクションしか開くことができません (ただし、トランザクションはネストできます)。
  • JDBC ResultSetは、データベース上の単一のカーソルによってサポートされます。ResultSet で close() が呼び出されると、カーソルが解放されます。
  • JDBC CallableStatementは、多くの場合 PL/SQL で記述されたデータベース上のストアド プロシージャを呼び出します。ストアド プロシージャは、0 個以上のカーソルを作成でき、カーソルを JDBC ResultSet として返すことができます。

JDBC はスレッド セーフです。さまざまな JDBC オブジェクトをスレッド間で受け渡ししても問題ありません。

たとえば、1 つのスレッドで接続を作成できます。別のスレッドがこの接続を使用して PreparedStatement を作成し、3 番目のスレッドが結果セットを処理できます。唯一の主な制限は、単一の PreparedStatement に対して複数の ResultSet を同時に開くことはできないということです。Oracle DB は接続ごとに複数の (並列) 操作をサポートしていますか?を参照してください。

データベースのコミットは接続で発生するため、その接続のすべての DML (INSERT、UPDATE、および DELETE) が一緒にコミットされることに注意してください。したがって、同時に複数のトランザクションをサポートする場合は、同時トランザクションごとに少なくとも 1 つの接続が必要です。

JDBC オブジェクトを閉じる

ResultSet を実行する典型的な例は次のとおりです。

Statement stmt = conn.createStatement();
try {
    ResultSet rs = stmt.executeQuery( "SELECT FULL_NAME FROM EMP" );
    try {
        while ( rs.next() ) {
            System.out.println( "Name: " + rs.getString("FULL_NAME") );
        }
    } finally {
        try { rs.close(); } catch (Exception ignore) { }
    }
} finally {
    try { stmt.close(); } catch (Exception ignore) { }
}

finally 句が close() によって発生した例外を無視する方法に注意してください。

  • try {} catch {} を使用せずに単に ResultSet を閉じると、失敗してステートメントが閉じられない可能性があります。
  • try の本体で発生した例外が呼び出し元に伝播できるようにします。たとえば、ステートメントの作成と実行などのループがある場合は、ループ内の各ステートメントを閉じることを忘れないでください。

Java 7 では、Oracle はAutoCloseable インターフェースを導入しました。これは、Java 6 ボイラープレートのほとんどを優れたシンタックス シュガーに置き換えます。

JDBC オブジェクトの保持

JDBC オブジェクトは、ローカル変数、オブジェクト インスタンス、およびクラス メンバーで安全に保持できます。一般に、次のことを行うことをお勧めします。

  • Connections や PreparedStatements など、長期間にわたって複数回再利用される JDBC オブジェクトを保持するには、オブジェクト インスタンスまたはクラス メンバーを使用します。
  • ResultSet にはローカル変数を使用します。これは、通常、単一の関数のスコープ内で取得、ループ、およびクローズされるためです。

ただし、例外が 1 つあります。EJB またはサーブレット/JSP コンテナを使用している場合は、厳密なスレッド モデルに従う必要があります。

  • Application Server のみがスレッドを作成します (受信要求を処理するため)。
  • Application Server のみが接続 (接続プールから取得) を作成します。
  • 呼び出し間で値 (状態) を保存するときは、十分に注意する必要があります。独自のキャッシュまたは静的メンバーに値を保存しないでください。これは、クラスターやその他の奇妙な状況では安全ではなく、アプリケーション サーバーがデータにひどいことをする可能性があります。代わりに、ステートフル Bean またはデータベースを使用してください。
  • 特に、異なるリモート呼び出しで JDBC オブジェクト (接続、ResultSet、PreparedStatements など) を保持しないでください。これはアプリケーション サーバーに管理させてください。Application Server は接続プールを提供するだけでなく、PreparedStatements もキャッシュします。

漏れをなくす

JDBC リークの検出と排除に役立つプロセスとツールが多数あります。

  1. 開発中 - バグを早期に発見することが断然最良の方法です。

    1. 開発の実践: 優れた開発の実践により、開発者のデスクを離れる前に、ソフトウェアのバグの数を減らすことができます。具体的な慣例は次のとおりです。

      1. 経験の浅い人を教育するためのペアプログラミング
      2. 多くの目が一人よりも優れているため、コードレビュー
      3. 単体テスト。これは、リークを簡単に再現できるテストツールからコードベースのすべてを実行できることを意味します
      4. 独自のライブラリを構築するのではなく、接続プーリングに既存のライブラリを使用する
    2. 静的コード分析: 優れたFindbugsのようなツールを使用して、静的コード分析を実行します。これにより、close() が正しく処理されていない多くの場所が検出されます。Findbugs には Eclipse 用のプラグインがありますが、1 回限りのスタンドアロンでも実行でき、Jenkins CI やその他のビルド ツールに統合されています。

  2. 実行時:

    1. 保持力とコミット

      1. ResultSet の保持可能性が ResultSet.CLOSE_CURSORS_OVER_COMMIT の場合、Connection.commit() メソッドが呼び出されると、ResultSet は閉じられます。これは、 Connection.setHoldability() を使用するか、オーバーロードされた Connection.createStatement() メソッドを使用して設定できます。
    2. 実行時のロギング。

      1. コードに適切なログ ステートメントを挿入します。これらは、顧客、サポート スタッフ、およびチームメイトがトレーニングを受けなくても理解できるように、明確で理解しやすいものにする必要があります。それらは簡潔で、処理ロジックを追跡できるように、主要な変数と属性の状態/内部値を出力する必要があります。良好なロギングは、アプリケーション、特にデプロイ済みのアプリケーションをデバッグするための基本です。
      2. プロジェクトにデバッグ JDBC ドライバーを追加できます (デバッグ用 - 実際にはデプロイしないでください)。1 つの例 (私は使用していません) はlog4jdbcです。次に、このファイルに対して簡単な分析を行って、対応するクローズがない実行を確認する必要があります。オープンとクローズをカウントすると、潜在的な問題があるかどうかが強調表示されます

        1. データベースの監視。SQL Developer の「Monitor SQL」機能やQuest の TOADなどのツールを使用して、実行中のアプリケーションを監視します。モニタリングについては、この記事で説明しています。監視中は、開いているカーソルを (テーブル v$sesstat などから) クエリし、それらの SQL を確認します。カーソルの数が増加し、(最も重要なこととして) 1 つの同一の SQL ステートメントが支配的になっている場合、その SQL にリークがあることがわかります。コードを検索して確認します。

他の考え

WeakReferences を使用して接続を閉じることはできますか?

弱い参照と弱い参照は、JVM が適切と判断したときにいつでも参照対象をガベージ コレクションできるようにする方法で、オブジェクトを参照できるようにする方法です (そのオブジェクトへの強い参照チェーンがないと仮定します)。

コンストラクターで ReferenceQueue をソフト参照またはウィーク参照に渡すと、オブジェクトが発生したときにオブジェクトが GC されると、オブジェクトは ReferenceQueue に配置されます (発生した場合)。このアプローチを使用すると、オブジェクトのファイナライズを操作でき、その時点でオブジェクトを閉じるかファイナライズできます。

ファントム参照は少し奇妙です。それらの目的はファイナライズを制御することだけですが、元のオブジェクトへの参照を取得することはできないため、そのオブジェクトで close() メソッドを呼び出すのは難しくなります。

ただし、GC がいつ実行されるかを制御しようとすることは、めったに良い考えではありません (Weak、Soft、および PhantomReferencesは、オブジェクトが GC のためにキューに入れられたという事実の後に通知します)。実際、JVM のメモリ量が大きい場合 (-Xmx2000m など)、オブジェクトを GC しない可能性があり、それでも ORA-01000 が発生します。JVM メモリがプログラムの要件に比べて小さい場合、ResultSet および PreparedStatement オブジェクトが作成直後 (それらから読み取る前) に GC され、プログラムが失敗する可能性があります。

TL;DR:弱参照メカニズムは、Statement および ResultSet オブジェクトを管理および閉じるのに適した方法ではありません。

于 2012-09-03T11:07:32.197 に答える
31

私はさらにいくつかの理解を追加しています。

  1. カーソルは、ステートメント オブジェクトのみに関するものです。結果セットでも接続オブジェクトでもありません。
  2. しかし、結果セットを閉じてオラクルのメモリを解放する必要があります。それでも、結果セットを閉じないと、CURSORS にはカウントされません。
  3. ステートメント オブジェクトを閉じると、結果セット オブジェクトも自動的に閉じられます。
  4. すべての SELECT/INSERT/UPDATE/DELETE ステートメントに対してカーソルが作成されます。
  5. 各 ORACLE DB インスタンスは、oracle SID を使用して識別できます。同様に、ORACLE DB は接続 SID を使用して各接続を識別できます。両方の SID が異なります。
  6. したがって、ORACLE セッションは jdbc(tcp) 接続に他なりません。これは 1 つの SID にすぎません。
  7. 最大カーソル数を 500 に設定すると、1 つの JDBC セッション/接続/SID のみになります。
  8. そのため、それぞれのカーソル (ステートメント) を使用して多くの JDBC 接続を行うことができます。
  9. JVM が終了すると、すべての接続/カーソルが閉じられるか、または JDBCConnection が閉じられ、その接続に関する CURSORS が閉じられます。

sysdba としてログインします。

Putty の場合 (Oracle ログイン):

  [oracle@db01 ~]$ sqlplus / as sysdba

SqlPlus では:

ユーザー名: sys as sysdba

session_cached_cursors の値を 0 に設定して、カーソルが閉じないようにします。

 alter session set session_cached_cursors=0
 select * from V$PARAMETER where name='session_cached_cursors'

DB の接続ごとに設定された既存の OPEN_CURSORS 値を選択します

 SELECT max(a.value) as highest_open_cur, p.value as max_open_cur FROM v$sesstat a, v$statname b, v$parameter p WHERE a.statistic# = b.statistic# AND b.name = 'opened cursors current' AND p.name= 'open_cursors'  GROUP BY p.value;

以下は、開いているカーソル値を持つ SID/接続リストを検索するクエリです。

 SELECT a.value, s.username, s.sid, s.serial#
 FROM v$sesstat a, v$statname b, v$session s
 WHERE a.statistic# = b.statistic#  AND s.sid=a.sid 
 AND b.name = 'opened cursors current' AND username = 'SCHEMA_NAME_IN_CAPS'

以下のクエリを使用して、開いているカーソル内の SQL を特定します。

 SELECT oc.sql_text, s.sid 
 FROM v$open_cursor oc, v$session s
 WHERE OC.sid = S.sid
 AND s.sid=1604
 AND OC.USER_NAME ='SCHEMA_NAME_IN_CAPS'

コードをデバッグしてお楽しみください!!! :)

于 2013-01-18T11:12:19.147 に答える
5

コードを次のように修正します。

try
{ //method try starts  
  String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
  pStmt = obj.getConnection().prepareStatement(sql);
  pStmt.setLong(1, subscriberID);
  for (String language : additionalLangs) {
    pStmt.setInt(2, Integer.parseInt(language));
    pStmt.execute();
  }
} //method/try ends
finally
{ //finally starts
   pStmt.close()
} 

pStatements、接続、および結果を本当に閉じていますか?

開いているオブジェクトを分析するには、ステートメント、接続、および結果オブジェクトの周りにコードをラップする委任パターンを実装できます。オブジェクトが正常に閉じられるかどうかがわかります。

例: pStmt = obj。getConnection ().prepareStatement(sql);

    class obj{ 

    public Connection getConnection(){
    return new ConnectionDelegator(...here create your connection object and put it into ...);

    } 
}


class ConnectionDelegator implements Connection{
    Connection delegates;

    public ConnectionDelegator(Connection con){
       this.delegates = con;
    }

    public Statement prepareStatement(String sql){
        return delegates.prepareStatement(sql);
    }

    public void close(){
        try{
           delegates.close();
        }finally{
           log.debug(delegates.toString() + " was closed");
        }
    }
}
于 2012-09-06T05:36:00.393 に答える
1

開いたSQLを見つけるためのクエリ。

SELECT s.machine, oc.user_name, oc.sql_text, count(1) 
FROM v$open_cursor oc, v$session s
WHERE oc.sid = s.sid
and S.USERNAME='XXXX'
GROUP BY user_name, sql_text, machine
HAVING COUNT(1) > 2
ORDER BY count(1) DESC
于 2013-01-21T21:21:51.150 に答える
1

この問題は主に、接続プールを使用しているときに発生します。接続を閉じると、その接続は接続プールに戻り、データベースへの接続がまだ開いているため、その接続に関連付けられているすべてのカーソルが閉じられないためです。したがって、1 つの代替手段は、プール内の接続のアイドル接続時間を減らすことです。接続が 10 秒間接続でアイドル状態になるたびに、データベースへの接続が閉じられ、新しい接続が作成されてプールに配置されます。

于 2014-09-14T16:25:20.717 に答える
1

autocommit=true に設定しましたか? そうでない場合は、これを試してください:

{ //method try starts  
    String sql = "INSERT into TblName (col1, col2) VALUES(?, ?)";
    Connection conn = obj.getConnection()
    pStmt = conn.prepareStatement(sql);

    for (String language : additionalLangs) {
        pStmt.setLong(1, subscriberID);
        pStmt.setInt(2, Integer.parseInt(language));
        pStmt.execute();
        conn.commit();
    }
} //method/try ends { 
    //finally starts
    pStmt.close()
} //finally ends 
于 2012-09-03T10:22:45.070 に答える
0

バッチ処理を使用すると、オーバーヘッドが少なくなります。例については、次のリンクを参照してください: http://www.tutorialspoint.com/jdbc/jdbc-batch-processing.htm

于 2012-09-17T07:15:22.693 に答える
0

1000回を超える反復でdbをクエリしていたため、同じ問題に直面しました。コードで try と finally を使用しました。しかし、まだエラーが発生していました。

これを解決するために、私は oracle db にログインして以下のクエリを実行しました:

ALTER SYSTEM SET open_cursors = 8000 SCOPE=BOTH;

そして、これは私の問題をすぐに解決しました。

于 2017-10-31T07:40:57.973 に答える