Ich muss mehr als 16 Millionen Datensätze aus einer Zeile mit mehr als 221 Millionen Zeilen löschen und es geht extrem langsam voran.
Ich freue mich, wenn Sie Vorschläge teilen, um den folgenden Code schneller zu machen:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @BATCHSIZE > 0
BEGIN
DELETE TOP (@BATCHSIZE) FROM MySourceTable
OUTPUT DELETED.*
INTO MyBackupTable
WHERE NOT EXISTS (
SELECT NULL AS Empty
FROM dbo.vendor AS v
WHERE VendorId = v.Id
);
SET @BATCHSIZE = @@ROWCOUNT;
SET @ITERATION = @ITERATION + 1;
SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);
PRINT @MSG;
COMMIT TRANSACTION;
CHECKPOINT;
END;
END TRY
BEGIN CATCH
IF @@ERROR <> 0
AND @@TRANCOUNT > 0
BEGIN
PRINT 'There is an error occured. The database update failed.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
Ausführungsplan (begrenzt auf 2 Iterationen)
VendorId
ist PK und nicht geclustert , wobei der Clustered-Index von diesem Skript nicht verwendet wird. Es gibt 5 andere nicht eindeutige, nicht gruppierte Indizes.
Aufgabe ist es, "Anbieter zu entfernen, die in einer anderen Tabelle nicht vorhanden sind" und sie in einer anderen Tabelle zu sichern. Ich habe 3 Tische vendors, SpecialVendors, SpecialVendorBackups
. Der Versuch, SpecialVendors
nicht in der Vendors
Tabelle vorhandene Datensätze zu entfernen und eine Sicherungskopie der gelöschten Datensätze zu erstellen, falls das, was ich tue, falsch ist und ich sie in ein oder zwei Wochen zurücksetzen muss.
quelle
Antworten:
Der Ausführungsplan zeigt, dass er Zeilen aus einem nicht gruppierten Index in einer bestimmten Reihenfolge liest und dann Suchvorgänge für jede gelesene äußere Zeile ausführt, um die zu bewerten
NOT EXISTS
Sie löschen 7,2% der Tabelle. 16.000.000 Zeilen in 3.556 Chargen von 4.500
Unter der Annahme, dass die qualifizierten Zeilen tatsächlich im gesamten Index verteilt sind, bedeutet dies, dass alle 13,8 Zeilen ca. 1 Zeile gelöscht wird.
Iteration 1 liest also 62.156 Zeilen und führt so viele Indexsuchen durch, bevor 4.500 zum Löschen gefunden werden.
In Iteration 2 werden 57.656 (62.156 - 4.500) Zeilen gelesen, die definitiv nicht qualifiziert sind, wenn gleichzeitige Aktualisierungen ignoriert werden (da sie bereits verarbeitet wurden), und dann weitere 62.156 Zeilen, um 4.500 zu löschen.
Iteration 3 liest (2 * 57.656) + 62.156 Zeilen und so weiter, bis schließlich Iteration 3.556 (3.555 * 57.656) + 62.156 Zeilen liest und so viele Suchvorgänge ausführt.
Die Anzahl der Indexsuchen, die über alle Stapel hinweg durchgeführt werden, beträgt also
SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)
Welches ist
((3555 * 3556 / 2) * 57656) + (3556 * 62156)
- oder364,652,494,976
Ich würde vorschlagen, dass Sie die zu löschenden Zeilen zuerst in einer temporären Tabelle materialisieren
Und ändern Sie das
DELETE
zu löschen.WHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)
Möglicherweise müssen Sie noch einNOT EXISTS
in dieDELETE
Abfrage selbst aufnehmen, um Aktualisierungen zu berücksichtigen, da die temporäre Tabelle ausgefüllt wurde. Dies sollte jedoch wesentlich effizienter sein, da nur 4.500 Suchvorgänge pro Stapel ausgeführt werden müssen.quelle
PK
Spalte? (Ich glaube, Sie schlagen mir vor, diese vollständig in dieDELETE TOP (@BATCHSIZE) FROM MySourceTable
sollte nurDELETE FROM MySourceTable
auch die temporäre Tabelle indizierenCREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );
und istVendorId
definitiv die PK für sich? Sie haben> 221 Millionen verschiedene Anbieter?Der Ausführungsplan schlägt vor, dass jede aufeinanderfolgende Schleife mehr Arbeit leistet als die vorherige Schleife. Unter der Annahme, dass die zu löschenden Zeilen gleichmäßig in der Tabelle verteilt sind, muss die erste Schleife etwa 4500 * 221000000/16000000 = 62156 Zeilen scannen, um 4500 zu löschende Zeilen zu finden. Es wird auch die gleiche Anzahl von Clustered-Index-Suchvorgängen für die
vendor
Tabelle ausgeführt. Die zweite Schleife muss jedoch über dieselben Zeilen von 62156 - 4500 = 57656 hinaus lesen, die Sie beim ersten Mal nicht gelöscht haben. Es ist zu erwarten, dass die zweite Schleife 120000 Zeilen ausMySourceTable
dervendor
Tabelle scannt und 120000 Suchvorgänge für die Tabelle ausführt . Der Arbeitsaufwand pro Schleife nimmt linear zu. Als Annäherung können wir sagen, dass die durchschnittliche Schleife 102516868 Zeilen vonMySourceTable
und lesen muss, um 102516868- Suchvorgänge gegen die durchzuführenvendor
Tabelle. Um 16 Millionen Zeilen mit einer Stapelgröße von 4500 zu löschen, muss Ihr Code 16000000/4500 = 3556 Schleifen ausführen. Der Gesamtaufwand für die Fertigstellung Ihres Codes beträgt also rund 364,5 Milliarden Zeilen, ausMySourceTable
denen gelesen wird, und 364,5 Milliarden Indexsuchen.Ein kleineres Problem ist, dass Sie eine lokale Variable
@BATCHSIZE
in einem TOP-Ausdruck ohne einenRECOMPILE
oder einen anderen Hinweis verwenden. Der Abfrageoptimierer kennt den Wert dieser lokalen Variablen beim Erstellen eines Plans nicht. Es wird davon ausgegangen, dass es gleich 100 ist. In Wirklichkeit löschen Sie 4500 Zeilen anstelle von 100, und aufgrund dieser Diskrepanz könnten Sie möglicherweise einen weniger effizienten Plan erhalten. Die niedrige Kardinalitätsschätzung beim Einfügen in eine Tabelle kann ebenfalls zu einem Leistungseinbruch führen. SQL Server wählt möglicherweise eine andere interne API zum Einfügen aus, wenn es der Meinung ist, dass 100 Zeilen anstelle von 4500 Zeilen eingefügt werden müssen.Eine Alternative besteht darin, einfach die Primärschlüssel / Clusterschlüssel der Zeilen, die Sie löschen möchten, in eine temporäre Tabelle einzufügen. Abhängig von der Größe Ihrer Schlüsselspalten kann dies leicht in Tempdb passen. In diesem Fall können Sie nur eine minimale Protokollierung erhalten , was bedeutet, dass das Transaktionsprotokoll nicht explodiert. Sie können auch eine minimale Protokollierung für jede Datenbank mit einem Wiederherstellungsmodell von erhalten
SIMPLE
. Weitere Informationen zu den Anforderungen finden Sie unter dem Link.Wenn dies keine Option ist, sollten Sie Ihren Code ändern, damit Sie den Clustered-Index für nutzen können
MySourceTable
. Der Schlüssel ist, Ihren Code so zu schreiben, dass Sie ungefähr die gleiche Menge Arbeit pro Schleife erledigen. Sie können dies tun, indem Sie den Index nutzen, anstatt die Tabelle jedes Mal von Anfang an zu scannen. Ich habe einen Blog-Beitrag geschrieben , in dem verschiedene Methoden des Loopings behandelt werden. Die Beispiele in diesem Beitrag fügen zwar in eine Tabelle ein, anstatt sie zu löschen, aber Sie sollten in der Lage sein, den Code anzupassen.Im folgenden Beispielcode gehe ich davon aus, dass der Primärschlüssel und der Clusterschlüssel von Ihnen
MySourceTable
. Ich habe diesen Code ziemlich schnell geschrieben und kann ihn nicht testen:Der Schlüsselteil ist hier:
Jede Schleife liest nur 60000 Zeilen aus
MySourceTable
. Dies sollte zu einer durchschnittlichen Löschgröße von 4500 Zeilen pro Transaktion und einer maximalen Löschgröße von 60000 Zeilen pro Transaktion führen. Wenn Sie mit einer kleineren Losgröße konservativer sein möchten, ist das auch in Ordnung. Die@STARTID
Variable rückt nach jeder Schleife vor, sodass Sie vermeiden können, dass dieselbe Zeile mehrmals aus der Quelltabelle gelesen wird.quelle
Zwei Gedanken fallen mir ein:
Die Verzögerung ist wahrscheinlich auf die Indizierung mit diesem Datenvolumen zurückzuführen. Versuchen Sie, die Indizes zu löschen, zu löschen und neu zu erstellen.
Oder..
Es kann schneller sein, die Zeilen, die Sie behalten möchten, in eine temporäre Tabelle zu kopieren, die Tabelle mit den 16 Millionen Zeilen zu löschen und die temporäre Tabelle umzubenennen (oder in eine neue Instanz der Quelltabelle zu kopieren).
quelle