Methoden zum Beschleunigen eines großen DELETE FROM <table> ohne Klauseln

37

Verwenden von SQL Server 2005.

Ich führe ein großes DELETE FROM ohne WHERE-Klauseln aus. Es entspricht im Grunde einer TRUNCATE TABLE-Anweisung - außer ich darf TRUNCATE nicht verwenden. Das Problem ist, dass die Tabelle riesig ist - 10 Millionen Zeilen, und die Fertigstellung dauert über eine Stunde. Gibt es eine Möglichkeit, es schneller zu machen, ohne:

  • Truncate verwenden
  • Indizes deaktivieren oder löschen?

Das T-Log befindet sich bereits auf einem separaten Datenträger.

Anregungen willkommen!

Tuseau
quelle
2
Wenn Sie dies häufig tun, sollten Sie die Tabelle
Gaius
1
Können Sie TRUNCATE nicht verwenden, weil es FK-Einschränkungen gibt, die auf die Tabelle verweisen?
Nick Chammas

Antworten:

39

Was Sie tun können, ist Batch-Löschungen wie folgt:

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

Wo xxx ist, sagen wir 50000

Eine Änderung davon, wenn Sie einen sehr hohen Prozentsatz von Zeilen entfernen möchten ...

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
gbn
quelle
3
@tuseau: Jeder Löschvorgang benötigt im Fehlerfall etwas Protokollspeicher, um einen Rollback durchzuführen. Das Löschen von 50.000 Zeilen erfordert weniger Ressourcen / Speicherplatz als das Löschen von 10 Millionen Zeilen. Natürlich laufen Log-Backups noch usw. und beanspruchen Speicherplatz, aber es ist auf dem Server einfacher, viele kleine Batches zu erstellen, als große Batches zu löschen.
15.
1
Danke, das Batch-Löschen hilft ein bisschen, ich denke, es ist die beste Option.
Tuseau
2
@Phil Helmer: Wenn sich das Löschen des Stapels in einer Transaktion befindet, wird es nicht verwendet. Andernfalls ist jeder Protokollschreibvorgang kleiner, was einfach eine einfachere Belastung darstellt
gbn
1
Ein weiterer Kommentar: Die Batch-Löschung hilft enorm und erfordert das Löschen von 20 Millionen Zeilen von 1 Stunde 42 Minuten auf 3 Minuten - ABER stellen Sie sicher, dass die Tabelle einen Clustered-Index hat! Wenn es sich um einen Heap handelt, erstellt die TOP-Klausel eine Sortierung im Ausführungsplan, die jede Verbesserung zunichte macht. Scheint danach offensichtlich.
Tuseau
2
@Noumenon: Es sorgt dafür ist @@ ROWCOUNT 1
GBN
21

Sie können die TOP-Klausel verwenden, um dies einfach zu erledigen:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END
SQLRockstar
quelle
Die geschweiften Klammern formatieren Ihren Code
gbn
@gbn Das ist auf SO. hier ist es noch 101 010.
bernd_k 15.03.11
7

Ich bin mit den Vorschlägen einverstanden, Ihre Löschvorgänge in überschaubare Blöcke aufzuteilen, wenn Sie TRUNCATE nicht verwenden können, und ich mag den Drop / Create-Vorschlag für seine Originalität, aber ich bin gespannt auf den folgenden Kommentar in Ihrer Frage:

Es entspricht im Grunde einer TRUNCATE TABLE-Anweisung - außer ich darf TRUNCATE nicht verwenden

Ich vermute, der Grund für diese Einschränkung hängt mit der Sicherheit zusammen, die gewährt werden muss, um eine Tabelle direkt abzuschneiden, und mit der Tatsache, dass Sie damit andere als die von Ihnen betroffenen Tabellen abschneiden können.

Unter der Annahme, dass dies der Fall ist, frage ich mich, ob eine gespeicherte Prozedur, die TRUNCATE TABLE verwendet und "EXECUTE AS" verwendet, eine praktikable Alternative zu Sicherheitsrechten ist, die zum direkten Abschneiden der Tabelle erforderlich sind.

Hoffentlich erhalten Sie auf diese Weise die Geschwindigkeit, die Sie benötigen, und können gleichzeitig die Sicherheitsbedenken Ihres Unternehmens in Bezug auf das Hinzufügen Ihres Kontos zur Rolle "db_ddladmin" berücksichtigen.

Ein weiterer Vorteil der Verwendung einer gespeicherten Prozedur auf diese Weise besteht darin, dass die gespeicherte Prozedur selbst gesperrt werden kann, sodass nur bestimmte Konten sie verwenden dürfen.

Wenn dies aus irgendeinem Grund keine akzeptable Lösung ist und Sie die Daten in dieser Tabelle einmal pro Tag / Stunde / usw. entfernen müssen, fordere ich Sie auf, einen SQL Agent-Job zu erstellen, um die Tabelle abzuschneiden zu einer festgelegten Zeit jeden Tag.

Hoffe das hilft!

Jeff
quelle
5

Außer abschneiden .. nur stapelweise löschen kann Ihnen helfen.

Sie können die Tabelle löschen und neu erstellen, wobei alle Einschränkungen und Indizes außer Kraft gesetzt sind. In Management Studio haben Sie die Möglichkeit, eine Tabelle per Skript zu löschen und zu erstellen. Dies sollte also eine triviale Option sein. Dies ist jedoch nur möglich, wenn Sie DDL-Aktionen ausführen dürfen, was meines Erachtens keine Option darstellt.

Marian
quelle
Da die Anwendung für gleichzeitige Operationen ausgelegt ist, sind das Ändern der Struktur (DDL) und die Verwendung von Truncate keine Optionen ... Ich denke, das Löschen von Stapeln ist die beste verfügbare Option. Trotzdem danke.
Tuseau
1

Da diese Frage eine so wichtige Referenz ist, veröffentliche ich diesen Code, der mir wirklich geholfen hat, das Löschen mit Schleifen und das Versenden von Nachrichten innerhalb einer Schleife zu verstehen, um den Fortschritt zu verfolgen.

Die Abfrage wird von dieser doppelten Frage geändert . Gutschrift auf @RLF für die Abfragebasis .

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;
Max xaM
quelle