SqlCommand.Prepare()を使用する意味と利点は何ですか?


14

SQLクエリの実行前にSqlCommand.Prepare()(MSDNを参照)メソッドが広く使用されている開発者コードに出会いました。そして、これの利点は何でしょうか?

サンプル:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

私は少し遊んでトレースしました。Prepare()メソッドを呼び出した後にコマンドを実行すると、SQL Serverで次のステートメントが実行されます。

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

その後、パラメータが値を取得してSqlCommand.ExecuteNonQuery()呼び出されると、次がSql-Serverで実行されます。

exec sp_execute 1,@id=20

私には、これはステートメントがPrepare()実行されるとすぐにコンパイルされるように見えます。これの利点は何でしょうか?これは、それがプランキャッシュに入れられ、目的のパラメーター値で最終クエリが実行されるとすぐに再利用できることを意味しますか?

SqlParametersで実行されるSqlCommandsは、常にプロシージャコールでラップされることがわかりました(別の質問で説明しました)sp_executesql。これにより、SQL Serverは、パラメーター値に依存しないプランを保存および再利用できます。

これに関してprepare()は、メソッドが役に立たないのか、時代遅れののか、ここで何かが足りないのでしょうか?


SQLインジェクションの防止に関係しているといつも思っていたので、特定のタイプの特定の変数を受け入れるようにクエリを準備します。そうすれば、これらのタイプの変数を渡さないと、クエリは失敗します
マークシンキンソン

とはいえ、このPHPの説明は、おそらく.NET でも
マークシンキンソン

「はい、運転手、出て行く準備をしてください。」「あなたは何を準備していますか?あなたはいつも準備をしています!ただ行ってください!」
すべての取引のジョン

回答:


19

準備されたSQLバッチを実行するのとは別にSQLバッチを準備することは、効果的な構成です**実行計画のキャッシュ方法を考えると、SQL Serverには役に立たない。準備(解析、パラメーターのバインド、コンパイル)のステップと実行の分離は、キャッシュがない場合にのみ意味があります。目的は、既存のプランを再利用することにより、解析とコンパイルに費やす時間を節約することです。これが内部プランキャッシュの機能です。ただし、すべてのRDBMSがこのレベルのキャッシングを行うわけではないため(これらの関数の存在から明らかです)、プランを「キャッシュする」ように要求するのはクライアントコードに任されているため、そのキャッシュIDを保存して再利用しますそれ。これは、クライアントコード(およびプログラマーがそれを実行して正しく実行することを忘れないでください)に対する追加の負担であり、不要です。実際、IDbCommand.Prepare()メソッドのMSDNページには次のように記載されています。

サーバーは、必要に応じて再利用の計画を自動的にキャッシュします。したがって、クライアントアプリケーションでこのメソッドを直接呼び出す必要はありません。

テストで示されている限り(質問で表示される内容と一致する)、SqlCommand.Prepare()を呼び出すことは、「準備」のみの操作実行しないことに注意してください。SQLを準備および実行するsp_prepexecを呼び出します。 ; 解析とコンパイルのみを行うsp_prepareを呼び出しません(そして、パラメーター値を取り込んでおらず、名前とデータ型のみを取り込んでいます)。したがって、呼び出しの「プリコンパイル」の利点はありませんSqlCommand.Prepareすぐに実行されるません(実行を延期することが目標であると仮定)。ただし、の代わりにSqlCommand.Prepare()呼び出す場合でも、呼び出すことの興味深い潜在的なマイナーな利点がsp_prepexecありsp_prepareます。注を参照してください(**)詳細については、下部にあります。

私のテストでは、SqlCommand.Prepare()呼び出されたとき(およびこの呼び出しがSQL Serverでコマンドを発行しないことに注意してください)、ExecuteNonQueryまたはExecuteReaderその次が完全に実行され、ExecuteReaderの場合は行を返すことを示しています。それでも、SQL Server Profilerは、SqlCommand.Execute______()呼び出し後の最初の呼び出しSqlCommand.Prepare()が「SQLの準備」イベントとして登録され、その後の呼び出しが.Execute___()として登録され呼び出しがとして登録することを示しています。


テストコード

次のURLのPasteBinにアップロードされています:http ://pastebin.com/Yc2Tfvupます。このコードは、SQL Server Profilerトレースの実行中(または拡張イベントセッション)に実行することを目的とした.NET / C#コンソールアプリを作成します。各ステップの後に一時停止するため、どのステートメントが特定の効果を持つかが明確になります。


更新

より多くの情報と、呼び出しを回避するわずかな潜在的な理由が見つかりましたSqlCommand.Prepare()。私のテストで気づいたことの1つと、そのテストコンソールアプリを実行している人に注意すべきこと:明示的な呼び出しは行われませんsp_unprepare。少し掘り下げて、MSDNフォーラムで次の投稿を見つけました。

SqlCommand-準備するかどうか

受け入れられた回答には多くの情報が含まれていますが、顕著なポイントは次のとおりです。

  • **準備によって実際に節約できるのは、主に、クエリ文字列をネットワーク経由で送信するのにかかる時間です。
  • 準備されたクエリには、キャッシュされたプランが保存される保証はなく、サーバーに到達するとアドホッククエリよりも高速ではありません。
  • RPC(CommandType.StoredProcedure)は準備から何も得ません。
  • サーバーでは、準備されたハンドルは基本的に、実行するTSQLを含むメモリ内マップへのインデックスです。
  • マップは接続ごとに保存されるため、相互接続は使用できません。
  • クライアントがプールからの接続を再利用するときに送信されるリセットにより、マップがクリアされます。

また、関連しているように見えますが、単にSqlClientがSqlConnectionの破棄時に正しくクリーンアップするのに対して、ODBCに固有の問題である可能性があるSQLCATチームからの次の投稿を見つけました。SQLCATの投稿には、接続プールのクリアなど、これを原因として証明するのに役立つ追加のテストが記載されていないため、言うのは困難です。

これらの準備されたSQLステートメントに注意してください


結論

とすれば:

  • 呼び出しの唯一の本当の利点はSqlCommand.Prepare()、ネットワーク経由でクエリテキストを再度送信する必要がないことです。
  • 接続のメモリ(つまり、SQL Serverメモリ)の一部としてクエリテキストを呼び出しsp_preparesp_prepexec保存する

電話することをお勧めしますSqlCommand.Prepare()唯一の潜在的な利点は、ネットワークパケットを節約することであり、一方でマイナス面はより多くのサーバーメモリを消費ため、ます。ほとんどの場合、消費されるメモリの量はおそらく非常に少ないですが、ほとんどのDBサーバーは10メガビット(最近では100メガビットまたはギガビット)接続でアプリサーバーに直接接続されているため、ネットワーク帯域幅はほとんど問題になりません(そして、いくつかは同じ箱にさえあります;-)。それとメモリはほとんどの場合、ネットワーク帯域幅よりも少ないリソースです。

複数のデータベースに接続できるソフトウェアを作成する人にとって、標準インターフェイスの利便性がこの費用/便益分析を変えるかもしれないと思います。しかし、その場合でも、基本的なオブジェクトであるため、異なるプロバイダー(したがって、呼び出す必要がないなどのプロバイダー間の違い)を可能にする「標準」DBインターフェースを抽象化するのはまだ十分に簡単であると信じる必要がありますPrepare()おそらくあなたのアプリのコードですでに行っている指向プログラミング;-)。


この広範な回答をありがとうございます。私はあなたのステップをテストしましたが、あなたの期待/結果から2つの異常がありました:ステップ4で追加の結果を得ました。ステップ10で、私は、ステップ2に比べて他のplan_handleました
Magier

1
@Magier確認ステップ4について...多分あなたは別のSETオプションを持っていないか、何とか2間のクエリテキストが異なっていましたか?単一の文字(可視または非可視)の違いであっても、異なる計画になります。このWebページからのコピーと貼り付けは役に立たない場合があるので、クエリ(単一引用符で囲まれたものすべて)をからsp_prepareの呼び出しにコピーしてくださいsp_executesql。ステップ10については、昨日より多くのテストを行いsp_prepare、の異なるハンドルでいくつかの機会を見ましたが、それでも同じではありませんsp_executesql。もう1つテストして、回答を更新します。
ソロモンラッツキー

1
@Magier実際、私が以前に実行したテストはそれをカバーしており、特にSQLのみをキャッシュするというMSDNフォーラムの投稿に照らして、プランの真の再利用はないと思います。私はまだplan_handleを再利用sp_executesqlできるのに、できない、またはできないのに興味があります。ただしsp_prepare、プランがキャッシュから削除されている場合は、解析時間はまだ残っているため、回答のその部分を削除します(興味深いが無関係です)。とにかく、その部分はあなたの直接の質問に対処しなかったので、より興味深いサイドノートでした。それ以外はすべて適用されます。
ソロモンラッツキー

1
これは私が探していた種類の説明でした。
-CSharpie

5

これに関しては、prepare()メソッドが役に立たないか時代遅れであるのか、ここで何かを見逃しているのでしょうか?

Prepareメソッドの値はSqlCommandの世界には制限があり、完全に役に立たないというわけではありません。1つの利点は、準備がSqlCommandが実装するIDbCommandインターフェイスの一部であることです。これにより、同じコードを、準備を呼び出す必要があるかどうかを判断する条件付きロジックなしで、他のDBMSプロバイダー(準備が必要な場合があります)に対して実行できます。

Prepareは、このユースケースAFAIK以外に、SQL Serverの.NETプロバイダーでは価値がありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.