maximum-open-cursors エラーである ORA-01000 は、Oracle データベース開発において非常に一般的なエラーです。Java のコンテキストでは、アプリケーションが、データベース インスタンスに構成されているカーソルよりも多くの ResultSet を開こうとしたときに発生します。
一般的な原因は次のとおりです。
設定ミス
- アプリケーションには、DB 上のカーソルよりも多くのスレッドがデータベースにクエリを実行しています。1 つのケースは、データベース上のカーソルの数よりも大きい接続とスレッド プールがある場合です。
- 多くの開発者またはアプリケーションが同じ DB インスタンス (おそらく多くのスキーマを含む) に接続されており、一緒に使用する接続が多すぎます。
解決:
カーソルリーク
- アプリケーションが 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 リークの検出と排除に役立つプロセスとツールが多数あります。
開発中 - バグを早期に発見することが断然最良の方法です。
開発の実践: 優れた開発の実践により、開発者のデスクを離れる前に、ソフトウェアのバグの数を減らすことができます。具体的な慣例は次のとおりです。
- 経験の浅い人を教育するためのペアプログラミング
- 多くの目が一人よりも優れているため、コードレビュー
- 単体テスト。これは、リークを簡単に再現できるテストツールからコードベースのすべてを実行できることを意味します
- 独自のライブラリを構築するのではなく、接続プーリングに既存のライブラリを使用する
静的コード分析: 優れたFindbugsのようなツールを使用して、静的コード分析を実行します。これにより、close() が正しく処理されていない多くの場所が検出されます。Findbugs には Eclipse 用のプラグインがありますが、1 回限りのスタンドアロンでも実行でき、Jenkins CI やその他のビルド ツールに統合されています。
実行時:
保持力とコミット
- ResultSet の保持可能性が ResultSet.CLOSE_CURSORS_OVER_COMMIT の場合、Connection.commit() メソッドが呼び出されると、ResultSet は閉じられます。これは、 Connection.setHoldability() を使用するか、オーバーロードされた Connection.createStatement() メソッドを使用して設定できます。
実行時のロギング。
- コードに適切なログ ステートメントを挿入します。これらは、顧客、サポート スタッフ、およびチームメイトがトレーニングを受けなくても理解できるように、明確で理解しやすいものにする必要があります。それらは簡潔で、処理ロジックを追跡できるように、主要な変数と属性の状態/内部値を出力する必要があります。良好なロギングは、アプリケーション、特にデプロイ済みのアプリケーションをデバッグするための基本です。
プロジェクトにデバッグ JDBC ドライバーを追加できます (デバッグ用 - 実際にはデプロイしないでください)。1 つの例 (私は使用していません) はlog4jdbcです。次に、このファイルに対して簡単な分析を行って、対応するクローズがない実行を確認する必要があります。オープンとクローズをカウントすると、潜在的な問題があるかどうかが強調表示されます
- データベースの監視。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 オブジェクトを管理および閉じるのに適した方法ではありません。