句のない巨大なDELETE FROM <table>を高速化する方法


37

SQL Server 2005を使用します。

where句のない巨大なDELETE FROMを実行しています。基本的にTRUNCATE TABLEステートメントと同等です-TRUNCATEの使用が許可されていないことを除きます。問題は、テーブルが巨大である-1000万行であり、完了するまでに1時間以上かかることです。以下なしで高速化する方法はありますか?

  • 切り捨ての使用
  • インデックスを無効化または削除しますか?

t-logはすでに別のディスクにあります。

どんな提案も歓迎します!


2
あなたはこれをたくさんやっている場合は、テーブルを分割を検討
ガイウス

1
テーブルを参照するFK制約があるため、TRUNCATEを使用できませんか?
ニックチャマス

回答:


39

できることは、次のようなバッチ削除です。

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

xxxは、たとえば、50000

非常に高い割合の行を削除したい場合は、これの修正...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable

3
@tuseau:削除するたびに、エラーが発生した場合にロールバックするためのログスペースが必要です。50k行の削除は、10m行の削除よりも少ないリソース/スペースで済みます。もちろん、ログのバックアップなどはまだ実行されており、スペースを取りますが、サーバーで大量の小さなバッチを処理する方が、大きなバッチをいじるよりも簡単です。
-gbn

1
おかげで、バッチ削除は少し役立ちます、私はそれが最良のオプションだと思います。
tuseau

2
@Phil Helmer:バッチ削除がトランザクション内にある場合、それを使用してもメリットはありません。それ以外の場合、各ログの書き込みは小さくなり、単純に負荷が軽減されます
-gbn

1
もう1つのコメント:バッチ削除は非常に役立ち、1時間42分から3分に2000万行を削除します-ただし、テーブルにクラスター化インデックスがあることを確認してください!ヒープの場合、TOP句は実行プランに並べ替えを作成しますが、これは改善を無効にします。後に明らかなようです。
tuseau

2
@Noumenon:@@ ROWCOUNTが1
gbn

21

これを簡単に行うには、TOP句を使用できます。

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END

中括弧はコードをフォーマットします
-gbn

@gbnそれはそうです。ここではまだ101 010です
。– bernd_k

7

TRUNCATEを使用できない場合、削除を管理可能なチャンクにバッチ処理する提案に同意し、独創性のためのドロップ/作成の提案が好きですが、質問の次のコメントに興味があります:

基本的にTRUNCATE TABLEステートメントと同等です - ただし、TRUNCATEの使用は許可されていません

この制限の理由は、テーブルを直接切り捨てるために付与する必要があるセキュリティと、関係するテーブル以外のテーブルを切り捨てることができるという事実に関係していると推測しています。

その場合、TRUNCATE TABLEを使用し、 "EXECUTE AS"を使用するストアドプロシージャを作成することは、テーブルを直接切り捨てるのに必要なセキュリティ権限を与えるための実行可能な代替手段と考えられますか。

願わくば、これで必要な速度が得られると同時に、アカウントをdb_ddladminロールに追加することで会社が抱えるセキュリティ上の懸念に対処できます。

この方法でストアドプロシージャを使用する別の利点は、特定のアカウントのみが使用できるようにストアドプロシージャ自体をロックダウンできることです。

何らかの理由でこれが許容できる解決策ではなく、このテーブルのデータを削除する必要がある場合は、1日1時間ごとなどに実行する必要がある場合は、テーブルを切り捨てるSQLエージェントジョブを作成するように要求します毎日予定された時間に。

お役に立てれば!


5

truncateを除く..バッチでの削除のみが役立ちます。

もちろん、すべての制約とインデックスを使用して、テーブルを削除して再作成できます。Management Studioには、ドロップして作成するテーブルをスクリプト化するオプションがあるため、簡単なオプションになります。ただし、これは、DDLアクションを実行することが許可されている場合のみです。これは、実際にはオプションではありません。


アプリケーションは同時操作用に設計されているため、構造(DDL)の変更と切り捨ての使用はオプションではありません...バッチ削除が最適な方法だと思います。どうもありがとう。
tuseau

1

この質問は非常に重要な参照なので、このコードを投稿して、ループで削除することと、進行状況を追跡するためにループ内でメッセージを送ることを理解するのに非常に役立ちました。

この重複した質問からクエリが変更されます。クエリベースの@RLFに対するクレジット。

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.