6

私はさまざまな場所を調べましたが、パフォーマンス上の利点のためだけであっても、どこよりもPreparedStatement優先されるべきであるという疑わしい主張をたくさん聞いてきました。s はバッチ処理されたステートメントのみに使用されるべきであり、それ以外には使用されるべきではないとStatement主張しています。PreparedStatement

しかし、私がフォローしてきた (主にオンラインの) 議論には盲点があるようです。具体的なシナリオを提示しましょう。


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

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

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

  • プールから接続を取得する
  • SQL文を準備する
  • 準備されたステートメントを実行する
  • 結果セットを処理し、該当する場合は閉じます
  • 準備されたステートメントを閉じる
  • 必要に応じて別のステートメントを準備し、同じ方法で処理する
  • 接続をプールに戻す

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


最近、ステートメントを準備 (解析) し、それを 1 回実行してクローズするという全体的な考え方は、本質的に の誤用でありPreparedStatement、サーバーまたはクライアントの準備済みステートメントが使用されているかどうかに関係なく、パフォーマンス上の利点はまったくないという意見を受け取りました。 (Oracle、DB2、MSSQL、MySQL、Derby など) は、そのようなステートメントを準備済みステートメント キャッシュにプロモートすることさえしません (少なくとも、デフォルトの JDBC ドライバー/データソースはプロモートしません)。

さらに、MySQL の開発環境で特定のシナリオをテストする必要がありましたが、Connector/J 使用状況アナライザーはこの考えに同意しているようです。バッチ化されていないすべての準備済みステートメントの場合、呼び出しは次のようにclose()出力します。

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


前述のアプリケーション設計上の選択によりPreparedStatement、接続プール内の各接続のすべてのイベントで使用されるすべての SQL ステートメントを保持するインスタンス キャッシュを持つことは、不適切な選択のように思えます。

誰かがこれについてさらに詳しく説明できますか? 「準備 - 実行 (1 回) - 閉じる」というロジックに欠陥があり、本質的に推奨されていませんか?

PS Connector/J に対してuseUsageAdvisor=trueandを明示的に指定し、 or のいずれかを使用すると、バッチ処理されていないすべての SQL ステートメントのインスタンスを呼び出すときに、効率に関する警告が表示されます。cachePrepStmts=trueuseServerPrepStmts=trueuseServerPrepStmts=falseclose()PreparedStatement

4

5 に答える 5

3

ロジックの準備 - 実行 [一度] - 閉じるには欠陥があり、本質的に推奨されていませんか?

それ自体は問題ではないと思います。特定の SQL ステートメントは、明示的に (PreparedStatement を使用) するか、「その場で」 (ステートメントを使用して) 、ある時点で「準備」する必要があります。一度だけ実行される何かに対して Statement の代わりに PreparedStatement を使用すると、オーバーヘッドが少し増える可能性がありますが、特に引用したステートメントが真である場合、関連するオーバーヘッドが重要になる可能性は低いです。

典型的な DBMS (Oracle、DB2、MSSQL、MySQL、Derby など) は、そのようなステートメントを準備済みステートメント キャッシュにプロモートすることさえしません (少なくとも、デフォルトの JDBC ドライバー/データソースはプロモートしません)。

落胆するのは、次のようなパターンです。

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

これは、PreparedStatement が 1 回だけ使用され、同じ SQL ステートメントが何度も準備されているためです。(ただし、SQL ステートメントとその実行計画が実際にキャッシュされている場合は、それほど大したことではないかもしれません。) それを行うためのより良い方法は次のとおりです。

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 回だけ準備され、数回使用されます。

于 2015-10-11T14:49:40.797 に答える
1

PreparedStatementsプログラムで作成するかどうかに関係なく必要なので、望ましいです。内部的には、クエリが実行されるたびにデータベースが作成します。プログラムで作成すると、そのハンドルが得られます。PreparedStatement毎回を作成して破棄しても、 を使用する場合に比べてオーバーヘッドはあまり増加しませんStatement

データベースがデータベースを作成するには、多大な労力が必要です (構文チェック、解析、権限チェック、最適化、アクセス戦略など)。1 つを再利用すると、後続の実行でこの作業がバイパスされます。

それらを捨てるのではなく、null 入力パラメーターを無視するなど、再利用できるような方法でクエリを作成してみてください。

where someCol = coalesce(?, someCol)

したがって、パラメーターをnull(つまり、「未指定」) に設定すると、条件は成功します)

または、毎回クエリを作成する必要がある場合は、作成されたクエリがキーである への参照を保持しPreparedStatementsMapヒットした場合はそれらを再利用します。for you マップの実装を使用しWeakHashMap<String, PreparedStatements>て、メモリ不足を防ぎます。

于 2015-10-11T14:48:42.980 に答える