-
Nie powinieneś aktualizować 10k wierszy w zestawie, chyba że masz pewność, że operacja powoduje blokowanie strony (ponieważ wiele wierszy na stronę jest częścią
UPDATE
operacja). Problem polega na tym, że eskalacja blokad (z blokad wiersza lub strony do tabeli) występuje przy 5000 blokadach . Najbezpieczniej jest więc trzymać go nieco poniżej 5000, na wypadek gdyby operacja korzystała z blokad rzędów. -
nie używać SET ROWCOUNT, aby ograniczyć liczbę wierszy, które będą modyfikowane. Mamy tu do czynienia z dwoma problemami:
-
Zostało to przestarzałe od czasu wydania SQL Server 2005 (11 lat temu):
Użycie SET ROWCOUNT nie wpłynie na instrukcje DELETE, INSERT i UPDATE w przyszłej wersji programu SQL Server. Unikaj używania SET ROWCOUNT z instrukcjami DELETE, INSERT i UPDATE w nowych pracach programistycznych i zaplanuj modyfikowanie aplikacji, które obecnie z niego korzystają. Aby uzyskać podobne zachowanie, użyj składni TOP
-
Może mieć wpływ nie tylko na stwierdzenie, z którym masz do czynienia:
Ustawienie opcji SET ROWCOUNT powoduje, że większość instrukcji Transact-SQL zatrzymuje przetwarzanie, gdy ma na nie wpływ określona liczba wierszy. Obejmuje to wyzwalacze. Opcja ROWCOUNT nie wpływa na dynamiczne kursory, ale ogranicza zestaw kluczy i niewrażliwych kursorów. Tej opcji należy używać ostrożnie.
Zamiast tego użyj
TOP ()
klauzula. -
-
Nie ma sensu przeprowadzać tutaj wyraźnej transakcji. To komplikuje kod i nie masz obsługi dla
ROLLBACK
, co nie jest nawet potrzebne, ponieważ każda instrukcja jest osobną transakcją (tj. automatyczne zatwierdzanie). -
Zakładając, że znajdziesz powód, aby zachować wyraźną transakcję, nie masz
TRY
/CATCH
Struktura. Proszę zobaczyć moją odpowiedź na DBA.StackExchange dlaTRY
/CATCH
szablon obsługujący transakcje:Czy jesteśmy zobowiązani do obsługi transakcji w kodzie C#, a także w procedurze sklepu
?
Podejrzewam, że prawdziwe WHERE
klauzula nie jest pokazana w przykładowym kodzie w pytaniu, więc po prostu polegając na tym, co zostało pokazane, lepiej model byłby:
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;
Testując @Rows
przeciwko @BatchSize
, możesz uniknąć ostatniej UPDATE
zapytanie (w większości przypadków), ponieważ ostateczny zestaw to zazwyczaj pewna liczba wierszy mniejsza niż @BatchSize
, w którym to przypadku wiemy, że nie ma już więcej do przetworzenia (co widzisz w danych wyjściowych pokazanych w odpowiedzi). Tylko w tych przypadkach, w których ostateczny zestaw wierszy jest równy @BatchSize
czy ten kod uruchomi ostateczną UPDATE
? wpływa na 0 wierszy.
Dodałem również warunek do WHERE
klauzula zapobiegająca ponownej aktualizacji wierszy, które zostały już zaktualizowane.
UWAGA DOTYCZĄCA WYDAJNOŚCI
Podkreśliłem powyżej „lepiej” (jak w „to jest lepsze model”), ponieważ zawiera on kilka ulepszeń w stosunku do oryginalnego kodu OP i działa dobrze w wielu przypadkach, ale nie jest idealny we wszystkich przypadkach. W przypadku tabel co najmniej określonego rozmiaru (który różni się w zależności od kilku czynników, więc mogę” a dokładniej), wydajność ulegnie pogorszeniu, ponieważ będzie mniej wierszy do naprawienia, jeśli:
- nie ma indeksu do obsługi zapytania lub
- jest indeks, ale co najmniej jedna kolumna w
WHERE
klauzula jest typem danych typu string, który nie używa sortowania binarnego, stądCOLLATE
Klauzula jest dodawana do zapytania w tym miejscu, aby wymusić sortowanie binarne, co powoduje unieważnienie indeksu (dla tego konkretnego zapytania).
Z taką sytuacją spotkał się @mikesigs, co wymaga innego podejścia. Zaktualizowana metoda kopiuje identyfikatory wszystkich wierszy, które mają zostać zaktualizowane, do tabeli tymczasowej, a następnie używa tej tabeli tymczasowej do INNER JOIN
do aktualizowanej tabeli w kolumnach klucza indeksu klastrowego. (Ważne jest, aby przechwytywać i dołączyć do indeksu klastrowego kolumny, niezależnie od tego, czy są to kolumny klucza podstawowego!).
Aby uzyskać szczegółowe informacje, zobacz odpowiedź @mikesigs poniżej. Podejście pokazane w tej odpowiedzi jest bardzo skutecznym schematem, z którego sam korzystałem przy wielu okazjach. Jedyne zmiany, które bym wprowadził, to:
- Jawnie utwórz
#targetIds
zamiast używaćSELECT INTO...
- Dla
#targetIds
tabeli, zadeklaruj klastrowany klucz podstawowy w kolumnie (kolumnach). - Dla
#batchIds
tabeli, zadeklaruj klastrowany klucz podstawowy w kolumnie (kolumnach). - Do wstawienia do
#targetIds
, użyjINSERT INTO #targetIds (column_name(s)) SELECT
i usuńORDER BY
ponieważ jest to niepotrzebne.
Tak więc, jeśli nie masz indeksu, którego można użyć do tej operacji i nie możesz tymczasowo utworzyć takiego, który faktycznie będzie działał (indeks filtrowany może działać, w zależności od Twojego WHERE
klauzula UPDATE
zapytanie), a następnie wypróbuj podejście pokazane w odpowiedzi @mikesigs (a jeśli korzystasz z tego rozwiązania, zagłosuj na nie).