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バッチの実行とは別にSQLバッチを準備することは、効果的な構成です** 実行プランのキャッシュ方法を考えると、SQL Serverでは役に立たない。準備(解析、パラメーターのバインド、およびコンパイル)と実行のステップを分離することは、キャッシングがない場合にのみ意味があります。その目的は、既存のプランを再利用することで、解析とコンパイルに費やされる時間を節約することであり、これが内部プランキャッシュの役割です。ただし、すべてのRDBMSがこのレベルのキャッシュを実行するわけではないため(これらの関数の存在から明らか)、プランを「キャッシュ」するように要求し、そのキャッシュIDを保存して再利用することは、クライアントコードに任されています。それ。これは、不要なクライアントコード(およびプログラマがそれを実行して正しく実行することを覚えておく必要がある)の追加の負担です。実際、 IDbCommand.Prepare() メソッドのMSDNページには次のように記載されています。
サーバーは、必要に応じて計画を自動的にキャッシュして再利用します。したがって、クライアントアプリケーションでこのメソッドを直接呼び出す必要はありません。
私のテストが(あなたが質問で示したものと一致する)限り、 SqlCommand.Prepare() を呼び出すと、しない「準備」のみの操作を実行します。これは sp_prepexec を呼び出し、を準備してSQLを実行します。 sp_prepare は呼び出されません。これは、解析とコンパイルのみを行います(パラメータ値を取りません。名前とデータ型のみを取りません)。したがって、_SqlCommand.Prepare
_を呼び出すことによる "プリコンパイル"の利点はありません。これは、即時実行を行うためです(実行を延期することが目的であると想定)。 [〜#〜]ただし[〜#〜]、_sp_prepexec
_を呼び出しても、SqlCommand.Prepare()
を呼び出すことには興味深い潜在的な小さなメリットがあります。 _sp_prepare
_の代わりに。注を参照してください(**)詳細は下部にあります。
私のテストでは、SqlCommand.Prepare()
が呼び出された場合でも(この呼び出しはSQL Serverでコマンドを発行しないないことに注意してください)、以下のExecuteNonQuery
またはExecuteReader
完全に実行され、ExecuteReaderの場合は行を返します。それでも、SQL Serverプロファイラーは、SqlCommand.Execute______()
呼び出しの後の最初のSqlCommand.Prepare()
呼び出しが「Prepare SQL」イベントとして登録され、後続の.Execute___()
呼び出しが「 Exec Prepared SQL "イベント。
テストコード
http://Pastebin.com/Yc2Tfvup でPastebinにアップロードされています。このコードは、SQL Server Profilerトレース(または拡張イベントセッション)の実行中に実行することを目的とした.NET/C#コンソールアプリを作成します。各ステップの後で一時停止するため、どのステートメントが特定の効果を持つかが明確になります。
[〜#〜]更新[〜#〜]
詳細情報、およびSqlCommand.Prepare()
の呼び出しを避けるためのわずかな理由が見つかりました。テストで気づいたことの1つと、そのテストコンソールアプリを実行しているすべての人に注意する必要があることは、_sp_unprepare
_を明示的に呼び出していないことです。掘り下げてみたところ、MSDNフォーラムに次の投稿が見つかりました。
受け入れられた回答には多くの情報が含まれていますが、重要な点は次のとおりです。
SQLCATチームから次の投稿も見つかりました。これは関連しているようですが、ODBCに固有の問題である可能性がありますが、SqlClientはSqlConnectionの破棄時に正しくクリーンアップします。 SQLCATの投稿では、接続プールのクリアなど、これを原因として証明するのに役立つ追加のテストについては触れられていないためです。
[〜#〜]結論[〜#〜]
とすれば:
SqlCommand.Prepare()
を呼び出すことの唯一の本当の利点は、ネットワーク経由でクエリテキストを再度送信する必要がないことです。sp_prepare
_と_sp_prepexec
_を呼び出すと、クエリテキストが接続のメモリ(SQL Serverメモリ)の一部として保存されます。againstSqlCommand.Prepare()
を呼び出すことをお勧めします。潜在的なメリットはネットワークパケットを節約することだけですが、マイナス面はサーバーメモリを多く消費します。ほとんどの場合、消費されるメモリの量はおそらく非常に少ないですが、ほとんどのDBサーバーは最低10メガビット(最近では100メガビットまたはギガビット)の接続を介してアプリサーバーに直接接続されているため、ネットワーク帯域幅が問題になることはめったにありません(そしていくつかは同じ箱にさえあります;-)。それとメモリは、ほとんどの場合、ネットワーク帯域幅よりも少ないリソースです。
私は、複数のデータベースに接続できるソフトウェアを書いている人なら誰でも、標準インターフェースの利便性がこのコスト/利益分析を変えるかもしれないと思います。しかし、その場合でも、さまざまなプロバイダーを許可する(したがって、必要としないなど、プロバイダー間の違いを可能にする「標準の」DBインターフェースを抽象化するのはまだ十分簡単であると信じなければなりませんPrepare()
)を呼び出すには、これが基本的なオブジェクト指向プログラミングであり、アプリのコードで既に実行している可能性が高いことを考えると、;-).
これに関して、prepare()メソッドが役に立たないか時代遅れであるかどうか、またはここで何か不足しているのでしょうか?
PrepareメソッドのSqlCommandの世界は限定的なものであり、まったく役に立たないというわけではありません。 1つの利点は、PrepareがSqlCommandが実装するIDbCommandインターフェイスの一部であることです。これにより、Prepareを呼び出すかどうかを決定する条件付きロジックなしで、同じコードを他のDBMSプロバイダー(Prepareを必要とする可能性がある)に対して実行できます。
SQL Serverの.NETプロバイダーでは、この使用例のほかに、準備は価値がありません。