web-dev-qa-db-ja.com

C#.NET(SQL Server)で準備されたステートメントを正しく効率的に再利用する方法

私はたくさんの質問を見ましたが、明らかに私のSO-fuは任務を果たせていないので、ここにいます。準備済みステートメントを効率的に使用しようとしています。単一のステートメントをパラメーター化するだけでなく、何度も再利用できるようにコンパイルすることを意味します。私の質問は、パラメーターと再利用について、そしてそれを正しく実装する方法についてです。

一般的に私はこの手順に従います(不自然な例):

_SqlConnection db = new SqlConnection(...);
SqlCommand s = new SqlCommand("select * from foo where a=@a", db);
s.Parameters.Add("@a", SqlDbType.VarChar, 8);
s.Prepare();
...
s.Parameters["@a"] = "bozo";
s.Execute();
_

スーパー、それは機能します。ただし、このクエリを実行するたびにこれらすべての手順(または後の4つ)を実行する必要はありません。それは準備されたステートメントの全体的な考えを打ち消しているようです。私の考えでは、パラメータを変更して再実行するだけでよいのですが、問題はその方法です。

私はs.Parameters.Clear()を試しましたが、これは実際には値だけでなくパラメーター自体も削除するので、本質的にパラメーターをre _Addとre _Prepareを再度必要とします。これも全体のポイントを壊すようです。結構です。

この時点で、_s.Parameters_を繰り返し処理し、すべてをnullまたはその他の値に設定することができます。 これで正しいですか?残念ながら、現在のプロジェクトには、1回の実行で約10,000回実行する必要がある〜15個のパラメーターを持つクエリがあります。このイテレーションをメソッドに振り分けることはできますが、これを行うためのより良い方法(ストアドプロシージャなし)があるかどうか疑問に思っていました。

現在の回避策は、すべてのパラメーターをnullに設定する拡張メソッド_SqlParameterCollection.Nullify_です。これは、私の場合には問題ありません。実行後にこれを実行します。


私はいくつかの事実上同一ですが(IMHO)未回答の質問を見つけました:

準備済みステートメントと.NETの組み込み接続プール

SQLite/C#接続プーリングと準備済みステートメントの混乱 (Sergeは答えに非常に近かったです!)

私が見つけることができる最良の答えは、(1)上記の常識と(2)このページです。

http://msdn.Microsoft.com/en-us/magazine/cc163799.aspx

15
Josh

準備されたSqlCommandを再利用する場合、確実に行う必要があるのは、パラメーター値を新しい値に設定することだけです。使用後にそれらを一掃する必要はありません。

私自身、過去10年間に作成されたDBMSでステートメントを準備することで顕著なメリットを得たことはありませんでした(DBサーバーがそのCPUの限界に達していたと思われますが、これは一般的ではありません)。準備は必要ですか?

同じコマンドを「1回の実行あたり10,000回まで」実行すると、外部ソースからアップロードしているのでない限り、少し匂いがします。その場合、一括読み込みが役立つでしょうか?それぞれの実行は何をしていますか?

乾杯-

10
simon at rcl

Simonの回答に追加するには、 Sql 2005より前Command.Prepare()は、アドホッククエリのクエリプランキャッシングを改善します(SPROCは通常コンパイルされます)。ただし、 より最近のSQLバージョン では、クエリがパラメーター化されている場合、パラメーター化されているアドホッククエリもキャッシュできるため、Prepare()の必要性が減少します。

次に、SqlParametersコレクションを保持して、変化するパラメーター値の値のみを変更し、パラメーターの繰り返し作成(つまり、パラメーターオブジェクトの作成とコレクションの保存)を防ぐ例を示します。

using (var sqlConnection = new SqlConnection("connstring"))
 {
    sqlConnection.Open();
    using (var sqlCommand = new SqlCommand
       {
          Connection = sqlConnection,
          CommandText = "dbo.MyProc",
          CommandType = CommandType.StoredProcedure,
       })
    {
       // Once-off setup per connection
       // This parameter doesn't vary so is set just once
       sqlCommand.Parameters.Add("ConstantParam0", SqlDbType.Int).Value = 1234;
       // These parameters are defined once but set multiple times
       sqlCommand.Parameters.Add(new SqlParameter("VarParam1", SqlDbType.VarChar));
       sqlCommand.Parameters.Add(new SqlParameter("VarParam2", SqlDbType.DateTime));

       // Tight loop - performance critical
       foreach(var item in itemsToExec)
       {
         // No need to set ConstantParam0
         // Reuses variable parameters, by just mutating values
         sqlParameters["VarParam1"].Value = item.Param1Value; // Or sqlParameters[1].Value
         sqlParameters["VarParam2"].Value = item.Param2Date; // Or sqlParameters[2].Value
         sqlCommand.ExecuteNonQuery();
       }
    }
}

注:

  • 多数の行を挿入する場合で、データベースの他のユーザーとの同時実行性が重要であり、ACIDトランザクション境界が重要でない場合は、テーブルで保持される行ロックが5000未満になるように更新をバッチ処理およびコミットすることを検討できます。一度に、テーブルロックのエスカレーションから保護します。
  • プロシージャが実際に実行している作業によっては、ループを並列化する機会があるかもしれません。 TPLで。明らかに、接続とコマンドはスレッドセーフではありません。各タスクには独自の接続と再利用可能なコマンドが必要です- Parallel.ForEachのlocalInitオーバーロードがこれに最適です
5
StuartLC