maximum-open-cursorsエラーであるORA-01000は、Oracleデータベース開発で非常に一般的なエラーです。 Javaのコンテキストでは、アプリケーションがデータベースインスタンスに設定されているカーソルよりも多くのResultSetを開こうとしたときに発生します。
一般的な原因は次のとおりです。
-
設定ミス
- アプリケーションには、DB上のカーソルよりも多くのスレッドがデータベースにクエリを実行しています。 1つのケースは、データベース上のカーソルの数よりも大きい接続とスレッドプールがある場合です。
- 同じDBインスタンス(おそらく多くのスキーマが含まれる)に接続されている多くの開発者またはアプリケーションがあり、一緒に使用している接続が多すぎます。
-
解決策:
- データベース上のカーソルの数を増やす(リソースが許す場合)または
- アプリケーションのスレッド数を減らします。
-
カーソルリーク
- アプリケーションは、ResultSet(JDBCの場合)またはカーソル(データベースのストアドプロシージャの場合)を閉じていません
- 解決策 :カーソルリークはバグです。 DB上のカーソルの数を増やすと、避けられない障害が遅れるだけです。リークは、静的コード分析、JDBCまたはアプリケーションレベルのログ記録、およびデータベース監視を使用して見つけることができます。
背景
このセクションでは、カーソルの背後にある理論の一部と、JDBCの使用方法について説明します。背景を知る必要がない場合は、これをスキップして「リークの排除」に直接進むことができます。
カーソルとは何ですか?
カーソルは、クエリの状態、具体的にはリーダーがResultSet内にある位置を保持するデータベース上のリソースです。各SELECT文にはカーソルがあり、PL / SQLストアド・プロシージャは必要な数のカーソルを開いて使用できます。 Orafaqでカーソルの詳細を確認できます。
データベースインスタンスは通常、いくつかの異なるスキーマを提供します 、さまざまなユーザー それぞれ複数のセッション 。これを行うために、すべてのスキーマ、ユーザー、およびセッションで使用できるカーソルの数が固定されています。すべてのカーソルが開いていて(使用中)、新しいカーソルを必要とする要求が入ってくると、要求は失敗し、ORA-010000エラーが発生します。
カーソル数の検索と設定
番号は通常、インストール時にDBAによって構成されます。現在使用されているカーソルの数、最大数、および構成には、OracleSQLDeveloperのAdministrator関数でアクセスできます。 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個以上のカーソルを作成でき、JDBCResultSetとしてカーソルを返すことができます。
JDBCはスレッドセーフです。スレッド間でさまざまなJDBCオブジェクトを渡すことはまったく問題ありません。
たとえば、1つのスレッドで接続を作成できます。別のスレッドがこの接続を使用してPreparedStatementを作成し、3番目のスレッドが結果セットを処理できます。唯一の大きな制限は、いつでも1つの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を単に閉じると、失敗してステートメントを閉じることができなくなる可能性があります
- 試行の本文で発生した例外を呼び出し元に伝播できるようにします。ステートメントの作成や実行などのループがある場合は、ループ内の各ステートメントを閉じることを忘れないでください。
Java 7では、OracleはAutoCloseableインターフェースを導入しました。これは、Java6ボイラープレートのほとんどをいくつかの優れた構文糖衣に置き換えます。
JDBCオブジェクトの保持
JDBCオブジェクトは、ローカル変数、オブジェクトインスタンス、およびクラスメンバーに安全に保持できます。一般的には、次のことをお勧めします。
- オブジェクトインスタンスまたはクラスメンバーを使用して、ConnectionsやPreparedStatementsなどの長期間にわたって複数回再利用されるJDBCオブジェクトを保持します
- ResultSetにはローカル変数を使用します。これは、ローカル変数が取得され、ループされてから、通常は単一の関数のスコープ内で閉じられるためです。
ただし、例外が1つあります。EJBまたはサーブレット/ JSPコンテナを使用している場合は、厳密なスレッドモデルに従う必要があります。
- アプリケーションサーバーのみがスレッドを作成します(これを使用して着信要求を処理します)
- アプリケーションサーバーのみが接続を作成します(接続プールから取得します)
- 呼び出し間で値(状態)を保存するときは、十分に注意する必要があります。自分のキャッシュや静的メンバーに値を保存しないでください。これは、クラスターやその他の奇妙な条件全体で安全ではなく、アプリケーションサーバーがデータにひどいことをする可能性があります。代わりに、ステートフルBeanまたはデータベースを使用してください。
- 特に、決して JDBCオブジェクト(Connections、ResultSets、PreparedStatementsなど)をさまざまなリモート呼び出しで保持します。これをアプリケーションサーバーに管理させます。アプリケーションサーバーは接続プールを提供するだけでなく、PreparedStatementsもキャッシュします。
リークの排除
JDBCリークの検出と排除に役立つプロセスとツールは多数あります。
-
開発中-バグを早期に発見することが最善のアプローチです:
-
開発慣行:優れた開発慣行は、ソフトウェアが開発者のデスクを離れる前に、ソフトウェアのバグの数を減らす必要があります。具体的な方法は次のとおりです。
- ペアプログラミング、十分な経験のない人を教育する
- 多くの目が1つよりも優れているため、コードレビュー
- ユニットテスト。これは、リークの再現を簡単にするテストツールからコードベースのすべてを実行できることを意味します
- 独自のライブラリを構築するのではなく、接続プールに既存のライブラリを使用する
-
静的コード分析:優れたFindbugsなどのツールを使用して、静的コード分析を実行します。これにより、close()が正しく処理されていない多くの場所が検出されます。 FindbugsにはEclipse用のプラグインがありますが、スタンドアロンで1回限り実行され、JenkinsCIやその他のビルドツールに統合されています
-
-
実行時:
-
保持性とコミット
- ResultSetの保持可能性がResultSet.CLOSE_CURSORS_OVER_COMMITの場合、Connection.commit()メソッドが呼び出されるとResultSetは閉じられます。これは、Connection.setHoldability()を使用するか、オーバーロードされたConnection.createStatement()メソッドを使用して設定できます。
-
実行時のロギング。
- コードに適切なログステートメントを配置します。これらは、顧客、サポートスタッフ、およびチームメートがトレーニングなしで理解できるように、明確で理解しやすいものでなければなりません。それらは簡潔で、処理ロジックをトレースできるように、主要な変数と属性の状態/内部値を出力することを含める必要があります。優れたロギングは、アプリケーション、特にデプロイされたアプリケーションをデバッグするための基本です。
-
デバッグJDBCドライバーをプロジェクトに追加できます(デバッグ用-実際にはデプロイしないでください)。 1つの例(私はそれを使用していません)はlog4jdbcです。次に、このファイルに対して簡単な分析を行って、対応するクローズがない実行を確認する必要があります。潜在的な問題がある場合は、開閉を数えることで強調表示されます
- データベースの監視。 SQLDeveloperの「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オブジェクトを管理および閉じるための適切な方法ではありません。