最大オープンカーソルエラーであるORA-01000は、Oracleデータベース開発で非常に一般的なエラーです。Javaのコンテキストでは、データベースインスタンスに設定されたカーソルよりも多くのResultSetをアプリケーションが開こうとしたときに発生します。
一般的な原因は次のとおりです。
設定ミス
- DB上のカーソルよりも、データベースをクエリするアプリケーション内のスレッドが多くなっています。1つのケースは、データベース上のカーソルの数よりも大きい接続とスレッドプールがある場合です。
- 多くの開発者またはアプリケーションが同じDBインスタンス(おそらく多くのスキーマを含む)に接続されており、一緒に使用している接続が多すぎます。
解決:
- データベースのカーソル数を増やす(リソースで可能な場合)または
- アプリケーションのスレッド数を減らします。
カーソルリーク
- アプリケーションがResultSets(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コンテナを使用している場合は、厳密なスレッドモデルに従う必要があります。
- アプリケーションサーバーのみがスレッドを作成します(スレッドを使用して着信要求を処理します)。
- アプリケーションサーバーのみが接続を作成します(接続プールから取得)
- 呼び出し間で値(状態)を保存するときは、非常に注意する必要があります。独自のキャッシュまたは静的メンバーに値を格納しないでください。これは、クラスター全体および他の奇妙な条件全体で安全ではなく、アプリケーションサーバーはデータに対してひどいことを行う可能性があります。代わりに、ステートフルBeanまたはデータベースを使用してください。
- 特に、さまざまなリモート呼び出しでJDBCオブジェクト(接続、ResultSet、PreparedStatementsなど)を保持しないでください。アプリケーションサーバーでこれを管理してください。Application Serverは接続プールを提供するだけでなく、PreparedStatementsもキャッシュします。
漏れをなくす
JDBCリークの検出と排除に役立つプロセスとツールがいくつかあります。
開発中-バグを早期にキャッチするのが最善の方法です。
開発プラクティス:優れた開発プラクティスは、ソフトウェアが開発者のデスクを離れる前に、ソフトウェアのバグの数を減らす必要があります。具体的な方法は次のとおりです。
- ペアプログラミング、十分な経験がない人を教育する
- 多くの目は1つよりも優れているため、コードレビュー
- 単体テスト。これは、リークの再現を簡単にするテストツールからコードベースの一部またはすべてを実行できることを意味します。
- 独自のライブラリを構築するのではなく、既存のライブラリを接続プーリングに使用します
静的コード分析:優れたFindbugsなどのツールを使用して、静的コード分析を実行します。これにより、close()が正しく処理されなかった多くの場所が選択されます。FindbugsにはEclipse用のプラグインがありますが、1回限りのスタンドアロンで実行され、Jenkins CIおよび他のビルドツールに統合されています
実行時:
保持可能性とコミット
- ResultSetの保持機能がResultSet.CLOSE_CURSORS_OVER_COMMITの場合、Connection.commit()メソッドが呼び出されると、ResultSetは閉じられます。これは、Connection.setHoldability()を使用するか、オーバーロードされたConnection.createStatement()メソッドを使用して設定できます。
実行時のロギング。
- コードに適切なログステートメントを挿入します。顧客、サポートスタッフ、チームメートがトレーニングなしで理解できるように、これらは明確で理解できるものでなければなりません。それらは簡潔で、処理ロジックをトレースできるように、主要な変数と属性の状態/内部値の出力を含む必要があります。優れたロギングは、特にデプロイされているアプリケーションをデバッグするための基本です。
デバッグ用のJDBCドライバーをプロジェクトに追加できます(デバッグ用-実際にはデプロイしないでください)。1つの例(私はそれを使用していません)はlog4jdbcです。次に、このファイルで簡単な分析を行い、どの実行に対応する終了がないかを確認する必要があります。オープンとクローズを数えることは、潜在的な問題があるかどうかを強調する必要があります
- データベースの監視。SQL開発者の「SQLの監視」機能やQuestのTOADなどのツールを使用して、実行中のアプリケーションを監視します。監視については、この記事で説明します。監視中に、開いているカーソル(テーブルv $ sesstatなど)に対してクエリを実行し、それらのSQLを確認します。カーソルの数が増加し、そして(最も重要なことに)1つの同一のSQLステートメントによって支配されるようになった場合、そのSQLにリークがあることがわかります。コードを検索して確認します。
他の考え
WeakReferencesを使用して接続のクローズを処理できますか?
弱参照とソフト参照は、JVMが適合と見なすときにいつでも参照をガベージコレクションできるようにオブジェクトを参照できるようにする方法です(そのオブジェクトへの強い参照チェーンがない場合)。
コンストラクター内のReferenceQueueをソフトまたはウィークリファレンスに渡す場合、オブジェクトが発生したときにオブジェクトがGCされたとき(発生した場合)、オブジェクトはReferenceQueueに配置されます。このアプローチを使用すると、オブジェクトのファイナライズを操作でき、その時点でオブジェクトをクローズまたはファイナライズできます。
ファントム参照は少し奇妙です。それらの目的はファイナライズを制御することだけですが、元のオブジェクトへの参照を取得することはできないため、そのオブジェクトでclose()メソッドを呼び出すのは困難です。
ただし、GCの実行を制御しようとすることはめったにありません(オブジェクトがGCのキューに入れられた後で、 Weak、Soft、およびPhantomReferencesから通知されます)。実際、JVMのメモリ容量が大きい場合(例:-Xmx2000m)、オブジェクトをGCしない可能性があり、ORA-01000が引き続き発生します。JVMメモリがプログラムの要件に比べて小さい場合、ResultSetオブジェクトとPreparedStatementオブジェクトが作成直後に(それらから読み取ることができる前に)GCされ、プログラムが失敗する可能性があります。
TL; DR:弱い参照メカニズムは、StatementオブジェクトとResultSetオブジェクトを管理して閉じるための良い方法ではありません。
for (String language : additionalLangs) {