web-dev-qa-db-ja.com

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

SQL Server 2005を使用します。

Where句のない巨大なDELETE FROMを実行しています。基本的にはTRUNCATE TABLEステートメントと同等ですが、TRUNCATEの使用は許可されていません。問題は、テーブルが巨大である-1,000万行であり、完了するまでに1時間以上かかる。なしでそれを速くする方法はありますか:

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

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

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

37
tuseau

あなたができることはこのようなバッチ削除です:

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
39
gbn

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

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

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

これは基本的にTRUNCATE TABLEステートメントと同等です-TRUNCATEの使用が許可されていない場合を除く

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

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

うまくいけば、これにより必要な速度が得られると同時に、アカウントをdb_ddladminロールに追加することで会社が抱えている可能性のあるセキュリティの問題にも対処できます。

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

なんらかの理由でこれが受け入れられない解決策であり、このテーブルのデータを削除する必要性が1日1時間に1回など必要な場合は、SQLエージェントジョブを作成してテーブルを切り捨てるように依頼します毎日予定時刻に。

お役に立てれば!

7
Jeff

Truncateを除いて..バッチでの削除のみが役立ちます。

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

5
Marian

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

クエリは this の重複する質問から変更されています。クエリベースの @ 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;
1
Max xaM