web-dev-qa-db-ja.com

SQL Server挿入パフォーマンス

このように生成される挿入クエリがあります

INSERT INTO InvoiceDetail (LegacyId,InvoiceId,DetailTypeId,Fee,FeeTax,Investigatorid,SalespersonId,CreateDate,CreatedById,IsChargeBack,Expense,RepoAgentId,PayeeName,ExpensePaymentId,AdjustDetailId) 
VALUES(1,1,2,1500.0000,0.0000,163,1002,'11/30/2001 12:00:00 AM',1116,0,550.0000,850,NULL,@ExpensePay1,NULL); 
DECLARE @InvDetail1 INT; SET @InvDetail1 = (SELECT @@IDENTITY);

このクエリは、110K行だけに対して生成されます。

これらすべてのクエリの実行には30分かかります

クエリプランを確認したところ、最大の%ノードは

57%のクエリコストでクラスター化されたインデックスの挿入。これには、投稿したくない長いxmlがあります。

クエリコストが38%のテーブルスプール

<RelOp AvgRowSize="35" EstimateCPU="5.01038E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="1" LogicalOp="Eager Spool" NodeId="80" Parallel="false" PhysicalOp="Table Spool" EstimatedTotalSubtreeCost="0.0466109">
  <OutputList>
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvoiceId" />
    <ColumnReference Database="[SkipPro]" Schema="[dbo]" Table="[InvoiceDetail]" Column="InvestigatorId" />
    <ColumnReference Column="Expr1054" />
    <ColumnReference Column="Expr1055" />
  </OutputList>
  <Spool PrimaryNodeId="3" />
</RelOp>

だから私の質問は、このことの速度を向上させるために私ができることは何ですか?クエリの前にALTER TABLE TABLENAME NOCHECK CONSTRAINTS ALLを実行し、クエリの後でALTER TABLE TABLENAME NOCHECK CONSTRAINTS ALLを実行しました。

そして、それは時間のほとんど何も削り落としませんでした。

SqlCommandオブジェクトを使用してクエリを送信する.NETアプリケーションでこれらのクエリを実行していることを知っています。

次に、sqlコマンドをファイルに出力し、sqlcmdを使用して実行しようとしましたが、動作を更新することができなかったため、あきらめました。

アイデアやヒント、助けはありますか?

更新:

わかりましたので、皆はとても役に立ちました。このような状況で、私は複数の答えを信用できればと思います。

これを修正する解決策は2つありました。

最初:

1)すべての外部キーを無効化/再有効化しました(ドロップするよりもはるかに簡単です)

ALTER TABLE TableName NOCHECK CONSTRAINT ALL
ALTER TABLE TableName CHECK CONSTRAINT ALL

2)インデックスを無効化/再有効化しました(ここでも、削除するよりもはるかに簡単です)

ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] DISABLE
ALTER INDEX [IX_InvoiceDetail_1] ON [dbo].[InvoiceDetail] REBUILD PARTITION = ALL WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, ONLINE = OFF, SORT_IN_TEMPDB = OFF )

二番目:

すべての挿入ステートメントを1つのトランザクションにラップしました。私は最初、.NETでそれを行う方法を知りませんでした。

私は得たすべての入力に本当に感謝しています。

このようなDBからDBへの変換を行う場合は、必ずBULK INSERTから始めます。はるかに柔軟で高速です。

19
Jose

挿入のように聞こえるため、SQL Serverはインデックスを再計算します。 1つ可能解決策は、インデックスを削除し、挿入を実行して、インデックスを再度追加することです。試行したソリューションでは、制約を無視するように指示した場合でも、インデックスを更新し続ける必要があります。

14
Jaxidian

これらのクエリを.Netクライアントから一度に1つずつ実行していますか(つまり、110,000の個別のクエリ要求をSQL Serverに送信していますか)?

その場合は、これらのINSERTをSQL Server自体ではなく、バッチ処理せずにSQL Serverに送信することによるネットワークの待ち時間やその他のオーバーヘッドが原因である可能性があります。

BULK INSERTを確認してください。

10
Patrick

ほとんどの場合、これはコミットフラッシュ待機です。 INSERTのセットを明示的に管理されたトランザクションにラップしない場合、各INSERTは独自の自動コミットされたトランザクションです。つまり、各INSERTは自動的にコミットを発行し、コミットはログが永続化される(ディスクに書き込まれる)まで待機する必要があります。各挿入後のログのフラッシュは非常に遅いです。

たとえば、1行のコミットスタイルで100k行を挿入しようとすると、次のようになります。

set nocount on; 
declare @start datetime = getutcdate();  

declare @i int = 0;
while @i < 100000
begin
INSERT INTO InvoiceDetail (
  LegacyId,InvoiceId,DetailTypeId,Fee,
  FeeTax,Investigatorid,SalespersonId,
  CreateDate,CreatedById,IsChargeBack,
  Expense,RepoAgentId,PayeeName,ExpensePaymentId,
  AdjustDetailId) 
  VALUES(1,1,2,1500.0000,0.0000,163,1002,
    '11/30/2001 12:00:00 AM',
    1116,0,550.0000,850,NULL,1,NULL); 
  set @i = @i+1;
end

select datediff(ms, @start, getutcdate());

これは私のサーバーで約12秒で実行されます。しかし、トランザクション管理を追加し、1000行ごとにコミットすると、100k行の挿入は約4秒しか持続しません。

set nocount on;  
declare @start datetime = getutcdate();  

declare @i int = 0;
begin transaction
while @i < 100000
begin
INSERT INTO InvoiceDetail (
  LegacyId,InvoiceId,DetailTypeId,
  Fee,FeeTax,Investigatorid,
  SalespersonId,CreateDate,CreatedById,
  IsChargeBack,Expense,RepoAgentId,
  PayeeName,ExpensePaymentId,AdjustDetailId) 
  VALUES(1,1,2,1500.0000,0.0000,163,1002,
    '11/30/2001 12:00:00 AM',
    1116,0,550.0000,850,NULL,1,NULL); 
  set @i = @i+1;
  if (@i%1000 = 0)
  begin
    commit
    begin transaction
  end  
end
commit;
select datediff(ms, @start, getutcdate());

また、バッチコミットなしでも12秒で100万行を挿入できることを考えると、30分必要ですが、調査する価値はあります。1)IOサブシステムの速度(たとえば、Avg. Sec per Transactionドライブに表示されます)および2)1つの呼び出しから@@ identityを取得してから次の挿入を呼び出すまでの間にクライアントコードは何をしていますか。時間の大部分がスタックのクライアント側にある可能性があります。簡単な解決策の1つは、複数の挿入を並行して起動すること(BeginExecuteNonQuery)で、SQL Serverの挿入を常にフィードすることです。

9
Remus Rusanu

この質問には「bulkinsert」というタグを付けています。それでは、なぜ BULK INSERT コマンドを使用しないのですか?

進行状況の更新が必要な場合は、一括挿入を小さな部分に分割し、各部分が完了した後に進行状況を更新できます。

6
Mark Byers

できることがいくつかあります。

1) Disable any triggers on this table
2) Drop all indexes
3) Drop all foreign keys
4) Disable any check constraints
4
Randy Minder

個々のINSERTの実行は、常に最も遅いオプションになります。また、@@ IDENTITYとの取り決めは何ですか?それらの間を追跡する必要があるようには見えません。

ファイルまたはSSISからのBULK INSERTを使用しない場合は、 ADO.NETのSqlBulkCopy機能 を使用します。プログラム。

11万行は、この回答を調べて書くよりもインポートにかかる時間が短いはずです。

4
Cade Roux

挿入パフォーマンスを向上させるためのいくつかの提案:

  • ADO.NET BatchSizeを増やす
  • ターゲットテーブルのクラスター化インデックスを適切に選択して、挿入によってクラスター化インデックスノードの分割が発生しないようにします(例:autoinc列)
  • 最初に一時ヒープテーブルに挿入してから、1つの大きな「select-by-select」ステートメントを発行して、ステージングテーブルのすべてのデータを実際のターゲットテーブルにプッシュします。
  • SqlBulkCopyを適用する
  • 挿入する前にテーブルロックを配置する(ビジネスシナリオで許可されている場合)

SqlServerでのLightning-Fast Insert Performanceのヒント から取得

3
Mabuse

うーん、実行して、パフォーマンスカウンターを確認してください。何が見えますか?どのようなディスクレイアウトがありますか? 30分で数百万行を挿入できます。正確には1億行近くです(リアルタイムの財務情報、他の3つのテーブルへのリンク)。私はあなたのIOレイアウトが悪い(すなわち、悪いディスク構造、悪いファイル配布)であることをかなり賭けます

1
TomTom