2015-10-11 16 views
5

私はさまざまな場所を見て、PreparedStatementからStatementまでの範囲が、パフォーマンスの利点のためだけであっても、好ましいとされるべきであるという不名誉な主張をたくさん聞いたことがあります。 PreparedStatementは、バッチ処理のために排他的に使用されなければならないという主張に至るまで、単一の実行の後にPreparedStatementを閉じる - それは設計上の欠陥ですか?

しかし、(私が主にオンラインで行った)ディスカッションには盲点があるようです。私は具体的なシナリオを提示しましょう。


DB接続プールを備えたEDA設計のアプリケーションがあります。イベントが来て、その中には永続性が必要なものもあれば、そうでないものもあります。一部は人為的に生成されます(たとえば、X分ごとに更新/リセットなど)。 いくつかのイベントが発生し、順番に処理されますが、永続性を必要とする他のタイプのイベントも同時に処理できます。

人為的に生成されたイベントとは別に、永続性を必要とするイベントがどのように到着するかの構造はありません。

このアプリケーションはかなり前に設計され(約2005年)、いくつかのDBMSをサポートしています。典型的なイベントハンドラ(永続性が必要とされる):

  • プール
  • からの接続は、SQL文を準備します
  • プロセスの結果セット、該当する場合は、
  • それを閉じ近いプリペアドステートメントを実行します準備されたステートメント
  • 必要に応じて同じ方法で別のステートメントを作成する
  • プールに戻る接続

イベントにバッチ処理が必要な場合は、文は一度作成され、addBatch/executeBatchのメソッドが使用されます。これは明白なパフォーマンス上の利点であり、これらのケースはこの質問に関連していません。


最近、私は、(解析)ステートメントを準備し、一度それを実行すると終了の全体的なアイデアは、PreparedStatementの誤用が本質的であること、意見を受けているにかかわらず、サーバーかどうかのゼロパフォーマンス上の利点を提供し、 (Oracle、DB2、MSSQL、MySQL、Derbyなど)は、プリペアドステートメントキャッシュ(または少なくともデフォルトのJDBCドライバ/データソースではない)にそのようなステートメントを宣言することさえありません。

また、MySQLの開発環境でいくつかのシナリオをテストしなければならなかったので、Connector/J使用アナライザはこの考えに同意しているようです。接続ごとに任意のイベントで使用されるすべての単一のSQL文を保持しているPreparedStatementインスタンスのキャッシュを持つ、

PreparedStatement created, but used 1 or fewer times. It is more efficient to prepare statements once, and re-use them many times


により、以前の概説アプリケーション設計の選択肢に:すべての非バッチ処理されたSQL文の場合、close()プリントを呼び出します接続プール内の貧しい人々の選択肢のように聞こえる。

誰かがこれについて詳しく説明できますか? 論理 "準備実行(一度) - クローズ"に欠陥がありますか?、本質的に推奨されていませんか?

P.S.明示的にConnector/JのためのuseUsageAdvisor=truecachePrepStmts=trueを指定し、useServerPrepStmts=trueまたはuseServerPrepStmts=falseすべての非バッチSQL文のclose()PreparedStatement上のインスタンスを呼び出すときに、まだ効率に関する警告につながるのいずれかを使用して。

+0

'PreparedStatement'に渡されるパラメータはどこから来ますか?多くの場合、 'PreparedStatement'はユーザの入力に基づいてステートメントを構築することを避けるために使用され、ステートメントの構造(例えばSQLインジェクション)を破る可能性があります。 – RealSkeptic

+0

はい、私は自動化入力墨塗りを認識しています。主に(整数)PKはイベントを伴います。時には生の入力(バイナリデータフィールドはフィールド型に基づいて解析され、 'setXxx'が挿入されます)、そうでなければ' setXxx'はグローバルまたはセッション(前述のシーケンシャルイベント)変数で呼び出すことができます。アプリケーション。しかし、疑問は、可能な副作用の可能性にかかわらず、単一のexec-then-closeロジックが推奨されていないかどうかです。 – afk5min

+1

すべては、DBシステムとドライバの内容によって異なります。ほとんどのドライバー(およびプール)は、それらを閉じるときでさえ、ステートメントキャッシングを実行すると述べています。したがって、あなたが長いスレッドを処理しているスレッド(俳優スタイル)を持っていなければ、PSを閉じるパターンに従います(接続が戻ったときにプールがそれをとるため)。 PSを開いたままにすることの最大の問題は、現在の接続でのみ機能することです。そのため、接続も保持する必要があります。 – eckes

答えて

1

PreparedStatementsは、プログラマチックに作成するかどうかにかかわらず1つが必要なため、これが望ましいです。内部的には、クエリが実行されるたびにデータベースが作成されます。プログラムで作成すると、そのクエリを処理できます。毎回PreparedStatementを作成して放棄しても、Statementを使用するとオーバーヘッドが増えません。

データベースでは、構文チェック、解析、アクセス許可のチェック、最適化、アクセス戦略などの作成に多大な労力が必要です。再利用することは、その後の処刑のためにこの努力をバイパスします。

代わりにそれらを離れて投げるのは、null入力パラメータを無視するなど、それが再利用できるような方法でクエリを書くのいずれか試してみてください。

where someCol = coalesce(?, someCol) 

ので、あなたがnullにパラメータを設定している場合(すなわち "指定されていない)、条件が成功した)

またはあなたは絶対に、クエリを毎回構築建てクエリが鍵となりMapPreparedStatementsへの参照を保持し、あなたがヒットを取得する場合は、それらを再利用する必要があります。のためにWeakHashMap<String, PreparedStatements>を使用してくださいメモリ不足を防ぐために実装をマップします。

+3

ただし、アプリケーションが実行するコマンドを送信する前に、ステートメントハンドルでの応答を待機する必要があるため、データベースへの余分なネットワークラウンドトリップが追加されます。大きな問題ではありませんが、留意する価値があります。 – Wyzard

+0

@Wyzardこれは良い点ですが、通常はクエリが非常に小さく(1つのフレーム)、特に結果セットと比較して、データベースは通常、ネットワーク内で「近くに」あり、データベースクエリのやりとりは非常に面白いです、ほとんどの場合、余分な往復は気付かれないかもしれません。 – Bohemian

2

ロジックは準備ができていますか?一度だけクローズすると欠陥があり、本質的に落胆しますか?

私は問題ではないと、それ自体がです。特定のSQLステートメントは、明示的に(PreparedStatementで)または「オンザフライで」(ステートメントで)いずれの時点で「準備」されている必要があります。そこ私たちは一度だけ実行されます何かを代わりに文のPreparedStatementを使用した場合に生じるほんの少しより多くのオーバーヘッドも、あなたが引用文が真である場合は特に、オーバーヘッド関与が重要であるとは考えにくいことがあります。

典型的なのDBMSは(Oracleは、DB2、MSSQLは、MySQLの、ダービーなど)も、プリペアドステートメントキャッシュに、そのような声明を促進しません(あるいは、少なくとも、そのデフォルトのJDBCドライバ/データソースではないでしょう)。が推奨され何

このようなパターンです:PreparedStatementのは一度だけ使用され、同じSQL文が何度も繰り返し準備されているので、

for (int thing : thingList) { 
    PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} "); 
    ps.setInt(1, thing); 
    ps.executeUpdate(); 
    ps.close(); 
} 

。 SQL文とそのexecutation計画が実際にキャッシュされている場合(偶数が、それは、大したことではないかもしれません。)それを行うには良い方法は

PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} "); 
for (int thing : thingList) { 
    ps.setInt(1, thing); 
    ps.executeUpdate(); 
} 
ps.close(); 

...または、より良い、「リソースとしてみてください」...

try (PreparedStatement ps = conn.prepareStatement(" {some constant SQL statement} ")) { 
    for (int thing : thingList) { 
     ps.setInt(1, thing); 
     ps.executeUpdate(); 
    } 
} 

注意して、これはさえバッチ処理を使用せずに真実であるということです。 SQLステートメントは、1度だけ準備され、何度か使用されます。

+0

実際にこのようなことについては、バッチ処理が有効かどうかを確認するために接続メタデータを使用し、autocommitを無効にして接続を取得し、PSを閉じる前に 'executeUpdate()'ではなく 'addBatch()'を使用し、 'executeBatch()接続を閉じる前に 'commit()'を実行してください。もちろん、メタデータでバッチがサポートされていないと表示された場合は、ここで説明するロジックに戻ります。 – afk5min

2

すでに述べたように、は最も高価な部分は、データベース内の文の解析です。文がすでに共有プールで解析されている場合、一部のデータベースシステム(これはDBにかなり依存しています - 私はOracleを主に話します)が利益をもたらすかもしれません。 (オラクルの用語では、これはソフトパースと呼ばれ、ハードパース - 新しいステートメントの解析)よりも安価です。準備されたステートメントを1回だけ使用しても、からの利益はソフト解析できます。

重要なのは、データベースに文を再利用することです。 カウンタの例は、Hibernateのコレクションに基づくINリストの処理です。

.. FROM T WHERE X in (?,?,?, … length based on the size of the collection,?,? ,?,?) 

この文は、コレクションのサイズが異なる場合は再利用できません。

実行中のアプリケーションによって生成されるSQLクエリのスペクトルの概要を把握するには、まず、V $ SQLビューが必要です。 PARSING_SCHEMA_NAMEを接続プール・ユーザーとともにフィルタリングし、SQL_TEXTおよびEXECUTIONSカウントを確認します。

二つの極端な状況は避けるべきである:異なるアクセスパスのステートメントを再利用

  • 渡すクエリテキスト内のパラメータ(IDS)(これはよく知られている)と

後者の例は、提供されたパラメータを使用して、テーブルの限定された部分へのインデックスアクセスを実行し、パラメータなしですべてのレコードを処理する必要がある(フルテーブルスキャン)クエリです。その場合、2つの異なるステートメントを作成することは間違いありません(両方の解析が異なる実行プランにつながるため)。

+0

さて、PSの再解析が少なくとも実装に依存していると聞いてうれしいです。アプリケーションが複数のDBMSと他の答えをサポートしているという事実と相まって、現在のアプローチは最適と思われます。 – afk5min

0

PreparedStatementが作成されましたが、1回以下使用されました。それは、一度書類を作成し、あなたが安全にこの警告を無視して、彼らに何回

私の事を再利用した方が効率的である、それは請求に似ていることは、週の最初の40時間を動作するように、より効率的です次の睡眠より56時間、7時間後に食べ、残りはあなたの自由時間です。

イベントごとに1つの実行が必要です。平均を向上させるには50を実行する必要がありますか?

+1

実際、これは完全に意味があります。 – afk5min