-
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. -
Das sollten Sie nicht Verwenden Sie SET ROWCOUNT, um die Anzahl der zu ändernden Zeilen zu begrenzen. Hier gibt es zwei Probleme:
-
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
-
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. -
-
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). -
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 einenTRY
/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:
- es gibt keinen Index zur Unterstützung der Abfrage, oder
- es gibt einen Index, aber mindestens eine Spalte im
WHERE
-Klausel ist ein String-Datentyp, der keine binäre Sortierung verwendet, daher einCOLLATE
-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:
- Erstellen Sie explizit die
#targetIds
Tabelle, anstattSELECT INTO...
zu verwenden - Für die
#targetIds
deklarieren Sie einen gruppierten Primärschlüssel für die Spalte(n). - Für die
#batchIds
deklarieren Sie einen gruppierten Primärschlüssel für die Spalte(n). - Zum Einfügen in
#targetIds
verwenden SieINSERT INTO #targetIds (column_name(s)) SELECT
und entfernen Sie denORDER 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).