これを行うための本当にひどい方法は、各INSERT
ステートメントを独自のバッチとして実行することです。
バッチ 1:
INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd);
バッチ 2:
INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked);
バッチ 3:
INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz);
バッチ 4:
INSERT INTO Entries (id, name) VALUES (4, 'Giorgi);
バッチ 5:
INSERT INTO Entries (id, name) VALUES (5, 'AMissico);
注:パラメータ化、エラー チェック、および説明目的で除外されたその他のニッチ ピック。
これは本当に、恐ろしい、恐ろしい方法です。毎回ネットワークの往復時間がかかるため、本当にひどいパフォーマンスになります。
はるかに優れた解決策は、すべてのINSERT
ステートメントを 1 つのバッチにまとめることです。
バッチ 1:
INSERT INTO Entries (id, name) VALUES (1, 'Ian Boyd');
INSERT INTO Entries (id, name) VALUES (2, 'Bottlenecked');
INSERT INTO Entries (id, name) VALUES (3, 'Marek Grzenkowicz');
INSERT INTO Entries (id, name) VALUES (4, 'Giorgi');
INSERT INTO Entries (id, name) VALUES (5, 'AMissico');
この方法では、1 往復だけで済みます。このバージョンでは、パフォーマンスが大幅に向上します。約 5 倍高速です。
さらに良いのは、次のVALUES
句を使用することです。
INSERT INTO Entries (id, name)
VALUES
(1, 'Ian Boyd'),
(2, 'Bottlenecked'),
(3, 'Marek Grzenkowicz'),
(4, 'Giorgi'),
(5, 'AMissico');
INSERT
これにより、5 つの個別の s バージョンよりもパフォーマンスが向上します。これにより、サーバーは得意なことを実行できます:セットでの操作:
- 各トリガーは一度だけ操作する必要があります
- 外部キーは一度チェックされます
- 一意の制約は一度チェックされます
SQL Server は一連のデータを操作するのが大好きです。それはバイキングです!
パラメータ制限
上記の T-SQL の例では、わかりやすくするためにすべてのパラメーター化が削除されています。しかし実際には、クエリをパラメータ化する必要があります
- サーバーが各 T-SQL バッチをコンパイルする必要がなくなるというパフォーマンスのボーナスはそれほど大きくありません (ただし、高速な一括インポート中は、解析時間の節約が実際に加算される可能性があります)。
- SQL インジェクションを避けるほどではありません。あなたはすでに優れた開発者であり、
QuotedString(firstName)
- ただし、サーバーのクエリ プラン キャッシュがギガバイト単位のアドホック クエリ プランであふれないようにするためです。(私は、SQL Server のワーキング セット、つまり RAM が 2 GB のパラメーター化されていない SQL クエリ プランであることを確認しました)
しかし、ブルーノには重要な点があります。SQL Server のドライバーでは、バッチに 2,100 個のパラメーターしか含めることができません。上記のクエリには 2 つの値があります。
@id、@name
1 回のバッチで 1,051 行をインポートすると、2,102 個のパラメーターになります。次のエラーが表示されます。
この RPC リクエストで指定されたパラメータが多すぎます
そのため、一度に 5 行または 10 行を挿入するのが一般的です。バッチごとに行を追加しても、パフォーマンスはそれほど向上しません。利益は減少します。
パラメーターの数を少なく保ち、T-SQL バッチ サイズの制限に近づくことはありません。VALUES
いずれにせよ、句は 1000 タプルに制限されているという事実もあります。
実装する
最初のアプローチは良いですが、次の問題があります。
- パラメータ名の衝突
- 無制限の行数 (2100 パラメーターの制限に達する可能性があります)
したがって、目標は次のような文字列を生成することです。
INSERT INTO Entries (id, name) VALUES
(@p1, @p2),
(@p3, @p4),
(@p5, @p6),
(@p7, @p8),
(@p9, @p10)
私は私のズボンの席であなたのコードを変更します
IEnumerable<Entry> entries = GetStuffToInsert();
SqlCommand cmd = new SqlCommand();
StringBuilder sql = new StringBuilder();
Int32 batchSize = 0; //how many rows we have build up so far
Int32 p = 1; //the current paramter name (i.e. "@p1") we're going to use
foreach(var entry in entries)
{
//Build the names of the parameters
String pId = String.Format("@p{0}", p); //the "Id" parameter name (i.e. "p1")
String pName = String.Format("@p{0}", p+1); //the "Name" parameter name (i.e. "p2")
p += 2;
//Build a single "(p1, p2)" row
String row = String.Format("({0}, {1})", pId, pName); //a single values tuple
//Add the row to our running SQL batch
if (batchSize > 0)
sb.AppendLine(",");
sb.Append(row);
batchSize += 1;
//Add the parameter values for this row
cmd.Parameters.Add(pID, System.Data.SqlDbType.Int ).Value = entry.Id;
cmd.Parameters.Add(pName, System.Data.SqlDbType.String).Value = entry.Name;
if (batchSize >= 5)
{
String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
sb.ToString();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
sb.Clear();
batchSize = 0;
p = 1;
}
}
//handle the last few stragglers
if (batchSize > 0)
{
String sql = "INSERT INTO Entries (id, name) VALUES"+"\r\n"+
sb.ToString();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}