Einzelne Anweisungen - DML, DDL usw. - sind Transaktionen für sich. Also ja, nach jeder Iteration der Schleife (technisch: nach jeder Anweisung) wurde alles, was diese UPDATE
Anweisung geändert hat, automatisch festgeschrieben.
Natürlich gibt es immer eine Ausnahme, oder? Es ist möglich, implizite Transaktionen über SET IMPLICIT_TRANSACTIONS zu aktivieren . In diesem Fall würde die erste UPDATE
Anweisung eine Transaktion starten, die Sie COMMIT
oder ROLLBACK
am Ende benötigen würden . Dies ist eine Einstellung auf Sitzungsebene, die in den meisten Fällen standardmäßig deaktiviert ist.
Müssen wir explizite BEGIN TRANSACTION / END TRANSACTION-Anweisungen hinzufügen, damit wir jederzeit abbrechen können?
Nein. Angesichts der Tatsache, dass Sie den Prozess stoppen und neu starten möchten, ist das Hinzufügen einer expliziten Transaktion (oder das Aktivieren impliziter Transaktionen) eine schlechte Idee, da das Stoppen des Prozesses möglicherweise vor dem Ausführen des Prozesses abgefangen wird COMMIT
. In diesem Fall müssten Sie das manuell ausgeben COMMIT
(wenn Sie sich in SSMS befinden) oder wenn Sie dies über einen SQL Agent-Job ausführen, haben Sie diese Möglichkeit nicht und erhalten möglicherweise eine verwaiste Transaktion.
Möglicherweise möchten Sie auch @CHUNK_SIZE
eine kleinere Zahl festlegen . Die Eskalation von Sperren erfolgt im Allgemeinen bei 5000 Sperren, die für ein einzelnes Objekt erfasst wurden. Abhängig von der Größe der Zeilen und wenn es sich um Zeilensperren oder Seitensperren handelt, überschreiten Sie möglicherweise diese Grenze. Wenn die Größe einer Zeile so ist, dass nur 1 oder 2 Zeilen pro Seite passen, treffen Sie dies möglicherweise immer, auch wenn Seiten gesperrt werden.
Wenn die Tabelle partitioniert ist, haben Sie die Möglichkeit, die LOCK_ESCALATION
in SQL Server 2008 eingeführte Option für die Tabelle AUTO
so festzulegen, dass beim Eskalieren nur die Partition und nicht die gesamte Tabelle gesperrt wird. Oder Sie können für jede Tabelle dieselbe Option festlegen DISABLE
, obwohl Sie diesbezüglich sehr vorsichtig sein müssten. Siehe ALTER TABLE für Details.
Hier finden Sie eine Dokumentation, die sich mit der Eskalation von Sperren und den Schwellenwerten befasst: Eskalation sperren (gilt für "SQL Server 2008 R2 und höhere Versionen"). Und hier ist ein Blog-Beitrag, der sich mit dem Erkennen und Beheben von Sperreneskalationen befasst: Sperren in Microsoft SQL Server (Teil 12 - Sperreneskalation) .
Unabhängig von der genauen Frage, aber im Zusammenhang mit der Abfrage in der Frage, gibt es hier einige Verbesserungen, die vorgenommen werden könnten (oder zumindest scheint es so, wenn man sie nur betrachtet):
Für Ihre Schleife ist die Ausführung WHILE (@@ROWCOUNT = @CHUNK_SIZE)
etwas besser, da, wenn die Anzahl der bei der letzten Iteration aktualisierten Zeilen geringer ist als der für UPDATE angeforderte Betrag, keine Arbeit mehr zu erledigen ist.
Wenn das deleted
Feld ist ein BIT
Datentyp, ist dann nicht dieser Wert bestimmt, ob oder nicht deletedDate
ist 2000-01-01
? Warum brauchst du beides?
Wenn diese beiden Felder neu sind und Sie sie NULL
so hinzugefügt haben, dass es sich um eine Online- / nicht blockierende Operation handeln könnte, und Sie sie jetzt auf ihren "Standard" -Wert aktualisieren möchten, war dies nicht erforderlich. Ab SQL Server 2012 (nur Enterprise Edition) sind das Hinzufügen von NOT NULL
Spalten mit einer DEFAULT-Einschränkung nicht blockierende Vorgänge, solange der Wert von DEFAULT eine Konstante ist. Wenn Sie die Felder noch nicht verwenden, NOT NULL
löschen Sie sie einfach und fügen Sie sie erneut mit einer DEFAULT-Einschränkung hinzu.
Wenn kein anderer Prozess diese Felder aktualisiert, während Sie dieses UPDATE ausführen, ist es schneller, wenn Sie die Datensätze, die Sie aktualisieren möchten, in die Warteschlange stellen und diese Warteschlange dann einfach abarbeiten. Die aktuelle Methode weist einen Leistungseinbruch auf, da Sie die Tabelle jedes Mal neu abfragen müssen, um den Satz zu erhalten, der geändert werden muss. Stattdessen können Sie Folgendes tun, indem Sie die Tabelle in diesen beiden Feldern nur einmal scannen und dann nur sehr gezielte UPDATE-Anweisungen ausgeben. Es gibt auch keine Strafe, wenn der Prozess zu irgendeinem Zeitpunkt gestoppt und später gestartet wird, da die anfängliche Population der Warteschlange einfach die Datensätze findet, die aktualisiert werden müssen.
- Erstellen Sie eine temporäre Tabelle (#FullSet), die nur die Schlüsselfelder aus dem Clustered-Index enthält.
- Erstellen Sie eine zweite temporäre Tabelle (#CurrentSet) derselben Struktur.
in #FullSet einfügen über SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
Das TOP(n)
ist da drin wegen der Größe der Tabelle. Mit 100 Millionen Zeilen in der Tabelle müssen Sie die Warteschlangentabelle nicht wirklich mit dem gesamten Schlüsselsatz füllen, insbesondere wenn Sie den Prozess von Zeit zu Zeit stoppen und später neu starten möchten. Stellen Sie also vielleicht n
1 Million ein und lassen Sie das bis zur Fertigstellung durchlaufen. Sie können dies jederzeit in einem SQL Agent-Job planen, der einen Satz von 1 Million (oder sogar weniger) ausführt und dann auf die nächste geplante Zeit wartet, um wieder aufgenommen zu werden. Sie können dann festlegen, dass alle 20 Minuten ausgeführt wird, damit zwischen den Sätzen ein gewisser Atemraum n
entsteht, der gesamte Vorgang jedoch unbeaufsichtigt abgeschlossen wird. Dann lassen Sie den Job einfach selbst löschen, wenn nichts mehr zu tun ist :-).
- Führen Sie in einer Schleife Folgendes aus:
- Füllen Sie die aktuelle Charge über so etwas wie
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- Führen Sie das UPDATE mit folgenden Elementen aus:
UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
- Löschen Sie den aktuellen Satz:
TRUNCATE TABLE #CurrentSet;
- In einigen Fällen ist es hilfreich, einen gefilterten Index hinzuzufügen, um die
SELECT
Einspeisung in die #FullSet
temporäre Tabelle zu unterstützen. Hier einige Überlegungen zum Hinzufügen eines solchen Index:
- Die WHERE-Bedingung sollte daher mit der WHERE-Bedingung Ihrer Abfrage übereinstimmen
WHERE deleted is null or deletedDate is null
- Zu Beginn des Prozesses stimmen die meisten Zeilen mit Ihrer WHERE-Bedingung überein, sodass ein Index nicht so hilfreich ist. Möglicherweise möchten Sie warten, bis die 50% -Marke erreicht ist, bevor Sie diese hinzufügen. Wie viel es hilft und wann es am besten ist, den Index hinzuzufügen, hängt natürlich von mehreren Faktoren ab. Es ist also ein bisschen Versuch und Irrtum.
- Möglicherweise müssen Sie STATS manuell AKTUALISIEREN und / oder den Index neu erstellen, um ihn auf dem neuesten Stand zu halten, da sich die Basisdaten häufig ändern
- Denken Sie daran, dass der Index, während er dem hilft
SELECT
, das verletzt, UPDATE
da es sich um ein anderes Objekt handelt, das während dieses Vorgangs aktualisiert werden muss, daher mehr E / A. Dies funktioniert sowohl bei der Verwendung eines gefilterten Index (der beim Aktualisieren von Zeilen kleiner wird, da weniger Zeilen mit dem Filter übereinstimmen) als auch beim Warten auf das Hinzufügen des Index (wenn dies am Anfang nicht besonders hilfreich sein wird, besteht kein Grund, dies zu tun die zusätzliche E / A).
UPDATE: In meiner Antwort auf eine Frage, die sich auf diese Frage bezieht, finden Sie die vollständige Implementierung der oben vorgeschlagenen Fragen, einschließlich eines Mechanismus zum Verfolgen des Status und zum sauberen Abbrechen: SQL Server: Aktualisieren von Feldern in einer großen Tabelle in kleinen Blöcken: So erhalten Sie Fortschritt / Status?