Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Wie aktualisiere ich eine große Tabelle mit Millionen von Zeilen in SQL Server?

  1. Sie sollten nicht 10.000 Zeilen in einem Satz aktualisieren, es sei denn, Sie sind sicher, dass die Operation Seitensperren erhält (da mehrere Zeilen pro Seite Teil des UPDATE sind Betrieb). Das Problem ist, dass eine Sperreneskalation (von Zeilen- oder Seiten- zu Tabellensperren) bei 5000 Sperren auftritt . Daher ist es am sichersten, es knapp unter 5000 zu halten, nur für den Fall, dass die Operation Zeilensperren verwendet.

  2. Das sollten Sie nicht Verwenden Sie SET ROWCOUNT, um die Anzahl der zu ändernden Zeilen zu begrenzen. Hier gibt es zwei Probleme:

    1. Es ist seit der Veröffentlichung von SQL Server 2005 (vor 11 Jahren) veraltet:

      Die Verwendung von SET ROWCOUNT wirkt sich nicht auf DELETE-, INSERT- und UPDATE-Anweisungen in einer zukünftigen Version von SQL Server aus. Vermeiden Sie die Verwendung von SET ROWCOUNT mit DELETE-, INSERT- und UPDATE-Anweisungen in neuen Entwicklungsarbeiten und planen Sie, Anwendungen zu ändern, die es derzeit verwenden. Verwenden Sie für ein ähnliches Verhalten die TOP-Syntax

    2. Es kann mehr als nur die Anweisung betreffen, mit der Sie es zu tun haben:

      Das Festlegen der Option SET ROWCOUNT bewirkt, dass die Verarbeitung der meisten Transact-SQL-Anweisungen beendet wird, wenn sie von der angegebenen Anzahl von Zeilen betroffen sind. Dazu gehören Trigger. Die Option ROWCOUNT wirkt sich nicht auf dynamische Cursor aus, schränkt jedoch das Rowset von Keyset- und insensitiven Cursorn ein. Diese Option sollte mit Vorsicht verwendet werden.

    Verwenden Sie stattdessen TOP () Klausel.

  3. Es hat keinen Zweck, hier eine explizite Transaktion zu haben. Es verkompliziert den Code und Sie haben keine Handhabung für ein ROLLBACK , was nicht einmal benötigt wird, da jede Anweisung eine eigene Transaktion ist (d. h. Auto-Commit).

  4. Angenommen, Sie finden einen Grund, die explizite Transaktion beizubehalten, dann haben Sie keinen TRY / CATCH Struktur. Bitte lesen Sie meine Antwort auf DBA.StackExchange für einen TRY / CATCH Vorlage, die Transaktionen verarbeitet:

    Müssen wir Transaktionen sowohl im C#-Code als auch in der Store-Prozedur handhaben

Ich vermute, dass das echte WHERE -Klausel wird im Beispielcode in der Frage nicht gezeigt, also verlassen Sie sich einfach auf das, was gezeigt wurde, ein besseres Modell wäre:

DECLARE @Rows INT,
        @BatchSize INT; -- keep below 5000 to be safe
    
SET @BatchSize = 2000;

SET @Rows = @BatchSize; -- initialize just to enter the loop

BEGIN TRY    
  WHILE (@Rows = @BatchSize)
  BEGIN
      UPDATE TOP (@BatchSize) tab
      SET    tab.Value = 'abc1'
      FROM  TableName tab
      WHERE tab.Parameter1 = 'abc'
      AND   tab.Parameter2 = 123
      AND   tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
      -- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
      -- that you don't skip differences that compare the same due to
      -- insensitivity of case, accent, etc, or linguistic equivalence.

      SET @Rows = @@ROWCOUNT;
  END;
END TRY
BEGIN CATCH
  RAISERROR(stuff);
  RETURN;
END CATCH;

Durch Testen von @Rows gegen @BatchSize , können Sie dieses abschließende UPDATE vermeiden -Abfrage (in den meisten Fällen), da der endgültige Satz normalerweise einige Zeilen weniger als @BatchSize enthält , in diesem Fall wissen wir, dass keine weiteren verarbeitet werden müssen (was Sie in der in Ihrer Antwort gezeigten Ausgabe sehen). Nur in den Fällen, in denen der letzte Zeilensatz gleich @BatchSize ist wird dieser Code ein abschließendes UPDATE ausführen betrifft 0 Zeilen.

Ich habe auch eine Bedingung zum WHERE hinzugefügt -Klausel, um zu verhindern, dass Zeilen, die bereits aktualisiert wurden, erneut aktualisiert werden.

HINWEIS ZUR LEISTUNG

Ich habe oben „besser“ betont (wie in „das ist ein besseres model"), da dies mehrere Verbesserungen gegenüber dem ursprünglichen Code des OP aufweist und in vielen Fällen gut funktioniert, aber nicht für alle Fälle perfekt ist. Für Tabellen mit mindestens einer bestimmten Größe (die aufgrund mehrerer Faktoren variiert, damit ich ' (um es genauer zu sagen), verschlechtert sich die Leistung, da weniger Zeilen zu reparieren sind, wenn entweder:

  1. es gibt keinen Index zur Unterstützung der Abfrage, oder
  2. es gibt einen Index, aber mindestens eine Spalte im WHERE -Klausel ist ein String-Datentyp, der keine binäre Sortierung verwendet, daher ein COLLATE -Klausel wird hier zur Abfrage hinzugefügt, um die binäre Sortierung zu erzwingen, wodurch der Index (für diese bestimmte Abfrage) ungültig wird.

Dies ist die Situation, auf die @mikesigs gestoßen ist, und erfordert daher einen anderen Ansatz. Die aktualisierte Methode kopiert die IDs für alle zu aktualisierenden Zeilen in eine temporäre Tabelle und verwendet dann diese temporäre Tabelle für INNER JOIN in die Tabelle, die für die Schlüsselspalte(n) des Clustered-Index aktualisiert wird. (Es ist wichtig, den Clustered Index zu erfassen und ihm beizutreten Spalten, unabhängig davon, ob dies die Primärschlüsselspalten sind oder nicht!).

Einzelheiten finden Sie unten in der Antwort von @mikesigs. Der in dieser Antwort gezeigte Ansatz ist ein sehr effektives Muster, das ich selbst bei vielen Gelegenheiten verwendet habe. Die einzigen Änderungen, die ich vornehmen würde, sind:

  1. Erstellen Sie explizit die #targetIds Tabelle, anstatt SELECT INTO... zu verwenden
  2. Für die #targetIds deklarieren Sie einen gruppierten Primärschlüssel für die Spalte(n).
  3. Für die #batchIds deklarieren Sie einen gruppierten Primärschlüssel für die Spalte(n).
  4. Zum Einfügen in #targetIds verwenden Sie INSERT INTO #targetIds (column_name(s)) SELECT und entfernen Sie den ORDER BY da es unnötig ist.

Wenn Sie also keinen Index haben, der für diese Operation verwendet werden kann, und vorübergehend keinen erstellen können, der tatsächlich funktioniert (ein gefilterter Index könnte funktionieren, abhängig von Ihrem WHERE -Klausel für das UPDATE Abfrage), versuchen Sie dann den in @mikesigs Antwort gezeigten Ansatz (und wenn Sie diese Lösung verwenden, stimmen Sie ihr bitte zu).