Wie lösche ich große Datenmengen in SQL ohne Protokoll?


127

Ich habe eine große Datentabelle. Diese Tabelle enthält 10 Millionen Datensätze.

Was ist der beste Weg für diese Abfrage

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:) Ich fürchte, es sei denn, Sie sind bereit, eine Art ETL zu schreiben, um alle Zeilen readTime> = dateadd (MONTH, -7, GETDATE ()) in eine andere Tabelle zu bringen und dann eine Truncate-Tabelle auszugeben und die Daten mit ETL zurückzusetzen , Sie könnten nicht verhindern, dass es in das Protokoll
schreibt

Die Protokollierung ist eine Alles-oder-Nichts-Funktion für ausfallsichere Transaktionen. Es ist buchstäblich nicht sinnvoll, für einige Vorgänge kein Protokoll zu haben, für andere jedoch nicht, da das Protokoll sonst unbrauchbar ist.
Erik Philips

1
Exportieren Sie die Daten, die Sie behalten möchten, kürzen Sie die Tabelle und importieren Sie sie dann wieder in
Bohemian

Eine andere Option wäre die Verwendung einer Tabellenvariablen, die nicht protokolliert wird. Speichern Sie daher Ihre Daten readTime> = dateadd (MONTH, -7, GETDATE ()) in einer Tabellenvariablen, kürzen Sie die ursprüngliche Tabelle und kopieren Sie die Daten aus der Tabellenvariablen zurück. Ich würde jedoch eine Sicherungskopie der Daten erstellen, falls etwas schief gehen sollte und die Tabelle versehentlich abgeschnitten wird. :) Und immer einen Testlauf Ihres Skripts in einer geringeren Umgebung durchführen.
TMNT2014

Antworten:


203
  1. Wenn Sie alle Zeilen in dieser Tabelle löschen, ist es am einfachsten, die Tabelle abzuschneiden

    TRUNCATE TABLE LargeTable
    GO

    Beim Abschneiden der Tabelle wird die Tabelle einfach geleert. Sie können die WHERE-Klausel nicht verwenden, um das Löschen der Zeilen zu begrenzen, und es werden keine Trigger ausgelöst.

  2. Wenn Sie dagegen mehr als 80-90 Prozent der Daten löschen, sagen Sie, wenn Sie insgesamt 11 Millionen Zeilen haben und 10 Millionen löschen möchten, besteht eine andere Möglichkeit darin, diese 1 Million Zeilen (Datensätze, die Sie behalten möchten) einzufügen ) zu einem anderen Staging-Tisch. Schneiden Sie diese große Tabelle ab und fügen Sie diese 1 Million Zeilen zurück.

  3. Wenn Berechtigungen / Ansichten oder andere Objekte, denen diese große Tabelle als zugrunde liegende Tabelle zugrunde liegt, nicht durch das Löschen dieser Tabelle beeinflusst werden, können Sie diese relativ kleine Anzahl der Zeilen in eine andere Tabelle übertragen, diese Tabelle löschen und eine andere Tabelle mit demselben Schema erstellen und diese importieren Zeilen zurück in diese ex-große Tabelle.

  4. Eine letzte Option, die mir in den Sinn kommt, besteht darin, die Datenbank zu ändern Recovery Mode to SIMPLEund dann Zeilen in kleineren Stapeln mit einer while-Schleife wie dieser zu löschen.

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

und vergessen Sie nicht, den Wiederherstellungsmodus wieder auf "Voll" zu ändern, und ich denke, Sie müssen ein Backup erstellen, damit es vollständig wirksam wird (der Änderungs- oder Wiederherstellungsmodus).


14
Denken Sie auch daran, dass Sie keine FKs zuordnen können, wenn Sie eine Tabelle abschneiden.
HLGEM

1
Aber wie können Sie sicher sein, dass Sie 80-90% der Daten löschen? Nehmen wir an, ich habe nur einen Wertebereich, der gelöscht werden soll. Und ich habe ein paar Tische. Also muss ich jeden von ihnen überprüfen und den Prozentsatz berechnen, und wenn er bei 30% liegt, ist diese Methode wahrscheinlich nicht sehr effektiv ... Ich versuche, eine optimale Lösung für einen unbekannten Fall zu finden.
Archont

7
@Archont optimal solution for unknown casedas ist der Traum, nicht wahr ? Leider können Sie nicht jede Krankheit mit einer Pille heilen. Ich habe einige mögliche Lösungen für verschiedene Szenarien vorgeschlagen. Leider gibt es hier keine Splitterkugel.
M.Ali

5
Bei Auswahl von Option 4 ist Folgendes zu beachten: Abhängig von der Verwendung der Tabelle ist es möglicherweise besser, weniger als 5000 Zeilen gleichzeitig zu löschen, um eine Eskalation der Sperren zu vermeiden .
Daniel

Wenn die Anzahl der zu löschenden Datensätze viel größer ist als die Anzahl der Datensätze, die in der Tabelle verbleiben, habe ich festgestellt, dass die einfache Auswahl der Datensätze in der temporären Tabelle, die in der ursprünglichen Tabelle verbleiben und die ursprüngliche Tabelle löschen und umbenennen, viel schneller ist. Vorausgesetzt, Sie verwenden nicht irgendwo einen Identitäts-ID-Fremdschlüssel.
Vladimir Bozic

95

Die Antwort von @ m-ali ist richtig, aber denken Sie auch daran, dass Protokolle stark wachsen können, wenn Sie die Transaktion nicht nach jedem Block festschreiben und einen Prüfpunkt ausführen. So würde ich es machen und diesen Artikel http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes als Referenz nehmen, mit Leistungstests und Grafiken:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Dies sollte die akzeptierte Antwort sein, falls der verfügbare Speicherplatz begrenzt ist. Ohne COMMIT TRANSACTIONund CHECKPOINTdie Protokolle wachsen immer noch. Vielen Dank, dass Sie dies klargestellt haben.
Gkoul

+1. Beachten Sie nur, dass Sie möglicherweise @Deleted_Rowsmit 10000 vergleichen möchten oder dass Sie möglicherweise eine Endlosschleife erhalten, da kleine Datenmengen auf unbestimmte Zeit gelöscht werden. Also WHILE (@Deleted_Rows = 10000)- sobald es keine vollständige "Seite" von Daten zum Löschen gab, wird es gestoppt. In Ihrer Implementierung wird WHILE (@Deleted_Rows > 0)die while-Schleife erneut ausgeführt, auch wenn nur eine Zeile gelöscht wurde, und bei der nächsten Ausführung werden möglicherweise auch ein oder zwei zu löschende Zeilen gefunden, was zu einer Endlosschleife führt.
NS du Toit

@NSduToit Die WHERE-Klausel berücksichtigt Datensätze, die mindestens 7 Monate alt sind, sodass es keine neuen Datensätze gibt, die diese Bedingung erfüllen, während Sie den Löschvorgang durchführen.
Francisco Goldenstein

@FranciscoGoldenstein Nun, das in der Abfrage verwendete Datum ist bei jeder Iteration anders, da Sie das Datum innerhalb der WHILESchleife selbst wiederholt berechnen : dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Möglicherweise auch für andere Anwendungsfälle als diesen - möglicherweise werden der zugrunde liegenden Tabelle neue Daten hinzugefügt, die zu neuen Datensätzen führen, die zwischen verschiedenen Iterationen der WHILESchleife gelöscht werden können .
NS du Toit

52

Sie können auch GO + verwenden, wie oft Sie dieselbe Abfrage ausführen möchten.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

Ich mag das, es funktioniert für mich. Ich habe versehentlich dieselbe Zeile 26 Millionen Mal in eine Tabelle eingefügt und musste alle Vorkommen löschen, die in einer einzigen Löschanweisung keinen Speicher mehr auf dem Server hatten. Das ist also eine gute Frage Wird die mittlere Schleife gestoppt, wenn die zu löschenden Zeilen ausgehen?
ScottC

2
@ScottC, es ist keine Schleife, es wiederholt nur die Abfrage (stapelartig) und wenn Ihnen die Zeilen ausgehen, kann nichts gelöscht werden. Aber es wird nicht aufhören. Sie erhalten so etwas wie (0 betroffene Zeile (n)), wenn die von Ihnen gelöschten Zeilen ausgehen.
Bunkerbuster

ah, ja, ich habe festgestellt, dass ungefähr 5 Minuten nachdem ich meine Frage gepostet habe, da mein Löschen abgeschlossen ist, danke, das war sehr hilfreich!
ScottC

1
Von welchem ​​MS SQL Server soll diese Syntax GO xxfunktionieren? Ich erhalte den Fehler "Gespeicherte Prozedur konnte nicht gefunden werden" . Ohne den GOBefehl funktioniert es aber gut.
Abel

3
Hmm, es scheint, als könnte ich es ausführen, und es läuft tatsächlich mehrmals, aber in MS SQL Mgt Studio zeigt es die rote geschweifte Linie mit dem erwähnten Fehler (aber F5-Lauf funktioniert dann)
Abel

11

@ Francisco Goldenstein, nur eine kleine Korrektur. Das COMMIT muss verwendet werden, nachdem Sie die Variable festgelegt haben, andernfalls wird das WHILE nur einmal ausgeführt:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

Diese Variante von M.Ali funktioniert gut für mich. Es löscht einige, löscht das Protokoll und wiederholt. Ich beobachte, wie der Baumstamm wächst, fällt und von vorne anfängt.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

Das war sehr nützlich! Ich habe es geändert, um die # of rowszu löschende Zeit und auch die WHEREKlausel zu parametrisieren . Klappt wunderbar!
Shiva

7

Wenn Sie bereit (und in der Lage) sind, eine Partitionierung zu implementieren, ist dies eine effektive Technik zum Entfernen großer Datenmengen mit geringem Laufzeitaufwand. Nicht kosteneffektiv für eine einmalige Übung.


4

Ich konnte innerhalb von Minuten 19 Millionen Zeilen aus meiner Tabelle mit 21 Millionen Zeilen löschen . Hier ist mein Ansatz.

Wenn diese Tabelle einen automatisch inkrementierenden Primärschlüssel enthält , können Sie diesen Primärschlüssel verwenden.

  1. Rufen Sie den Mindestwert des Primärschlüssels der großen Tabelle ab, in der readTime <dateadd (MONTH, -7, GETDATE ()) ist. (Fügen Sie einen Index für readTime hinzu, falls dieser noch nicht vorhanden ist. Dieser Index wird ohnehin zusammen mit der Tabelle in Schritt 3 gelöscht.) Speichern wir es in einer Variablen 'min_primary'

  2. Fügen Sie alle Zeilen mit dem Primärschlüssel> min_primary in eine Staging-Tabelle ein (Speichertabelle, wenn die Anzahl der Zeilen nicht groß ist).

  3. Lass den großen Tisch fallen.

  4. Erstellen Sie die Tabelle neu. Kopieren Sie alle Zeilen von der Staging-Tabelle in die Haupttabelle.

  5. Lassen Sie die Staging-Tabelle fallen.


3

Sie können kleine Stapel mit einer while-Schleife löschen.

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Eine andere Verwendung:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

Optional;

Wenn das Transaktionsprotokoll aktiviert ist, deaktivieren Sie die Transaktionsprotokolle.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Kürzere Syntax

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

Wenn Sie SQL Server 2016 oder höher verwenden und in Ihrer Tabelle Partitionen basierend auf der Spalte erstellt werden, die Sie löschen möchten (z. B. Zeitstempelspalte), können Sie diesen neuen Befehl verwenden, um Daten nach Partitionen zu löschen.

TRUNCATE TABLE WITH (PARTITIONEN ({|} [, ... n]))

Dies löscht nur die Daten in ausgewählten Partitionen und sollte die effizienteste Methode zum Löschen von Daten aus einem Teil der Tabelle sein, da keine Transaktionsprotokolle erstellt werden und dies genauso schnell wie beim regulären Abschneiden erfolgt, ohne dass alle Daten gelöscht werden vom Tisch.

Nachteil ist, wenn Ihre Tabelle nicht mit Partition eingerichtet ist, müssen Sie auf die alte Schule gehen und die Daten mit regelmäßigem Ansatz löschen und dann die Tabelle mit Partitionen neu erstellen, damit Sie dies in Zukunft tun können, was ich auch getan habe. Ich habe die Partitionserstellung und -löschung in das Einfügeverfahren selbst eingefügt. Ich hatte eine Tabelle mit 500 Millionen Zeilen, daher war dies die einzige Option, um die Löschzeit zu verkürzen.

Weitere Informationen finden Sie unter den folgenden Links: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL Server 2016 Tabelle mit Partitionen abschneiden

Im Folgenden habe ich zuerst die Daten gelöscht, bevor ich die Tabelle mit Partitionen mit den erforderlichen Daten neu erstellen konnte. Diese Abfrage wird während des angegebenen Zeitfensters tagelang ausgeführt, bis die Daten gelöscht werden.

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Wenn ich ohne Schleife sage, kann ich GOTOAnweisung verwenden, um eine große Anzahl von Datensätzen mit SQL Server zu löschen. exa.

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

Auf diese Weise können Sie große Datenmengen mit kleinerer Löschgröße löschen.

Lassen Sie mich wissen, wenn Sie weitere Informationen benötigen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.