Werden in dieser while-Schleife explizite Transaktionen benötigt?

11

SQL Server 2014:

Wir haben eine sehr große Tabelle (100 Millionen Zeilen) und müssen einige Felder aktualisieren.

Für den Protokollversand usw. möchten wir ihn natürlich auch auf Transaktionen in Bissgröße beschränken.

Wenn wir das Folgende ein wenig laufen lassen und dann die Abfrage abbrechen / beenden, wird die bisher geleistete Arbeit dann alle festgeschrieben, oder müssen wir explizite BEGIN TRANSACTION / END TRANSACTION-Anweisungen hinzufügen, damit wir sie jederzeit abbrechen können?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END
Jonesome stellt Monica wieder her
quelle

Antworten:

13

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 UPDATEAnweisung 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 UPDATEAnweisung eine Transaktion starten, die Sie COMMIToder ROLLBACKam 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_SIZEeine 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_ESCALATIONin SQL Server 2008 eingeführte Option für die Tabelle AUTOso 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):

  1. 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.

  2. Wenn das deletedFeld ist ein BITDatentyp, ist dann nicht dieser Wert bestimmt, ob oder nicht deletedDateist 2000-01-01? Warum brauchst du beides?

  3. Wenn diese beiden Felder neu sind und Sie sie NULLso 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 NULLSpalten 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 NULLlöschen Sie sie einfach und fügen Sie sie erneut mit einer DEFAULT-Einschränkung hinzu.

  4. 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.

    1. Erstellen Sie eine temporäre Tabelle (#FullSet), die nur die Schlüsselfelder aus dem Clustered-Index enthält.
    2. Erstellen Sie eine zweite temporäre Tabelle (#CurrentSet) derselben Struktur.
    3. 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 n1 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 nentsteht, der gesamte Vorgang jedoch unbeaufsichtigt abgeschlossen wird. Dann lassen Sie den Job einfach selbst löschen, wenn nichts mehr zu tun ist :-).

    4. Führen Sie in einer Schleife Folgendes aus:
      1. Füllen Sie die aktuelle Charge über so etwas wie DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. 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;
      4. Löschen Sie den aktuellen Satz: TRUNCATE TABLE #CurrentSet;
  5. In einigen Fällen ist es hilfreich, einen gefilterten Index hinzuzufügen, um die SELECTEinspeisung in die #FullSettemporäre Tabelle zu unterstützen. Hier einige Überlegungen zum Hinzufügen eines solchen Index:
    1. Die WHERE-Bedingung sollte daher mit der WHERE-Bedingung Ihrer Abfrage übereinstimmen WHERE deleted is null or deletedDate is null
    2. 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.
    3. 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
    4. Denken Sie daran, dass der Index, während er dem hilft SELECT, das verletzt, UPDATEda 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?

Solomon Rutzky
quelle
Ihre Vorschläge in # 4 sind in einigen Fällen möglicherweise schneller, aber das scheint eine erhebliche Komplexität des Codes zu sein. Ich würde es vorziehen, einfach anzufangen, und wenn dies nicht Ihren Anforderungen entspricht, sollten Sie Alternativen in Betracht ziehen.
Bacon Bits
@ BaconBits Einverstanden, einfach zu starten. Um fair zu sein, sollten diese Vorschläge nicht für alle Szenarien gelten. Die Frage betrifft den Umgang mit einer sehr großen Tabelle (über 100 Millionen Zeilen).
Solomon Rutzky