Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Jak zaktualizować dużą tabelę z milionami wierszy w SQL Server?

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

  2. nie używać SET ROWCOUNT, aby ograniczyć liczbę wierszy, które będą modyfikowane. Mamy tu do czynienia z dwoma problemami:

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

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

  3. 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).

  4. Zakładając, że znajdziesz powód, aby zachować wyraźną transakcję, nie masz TRY / CATCH Struktura. Proszę zobaczyć moją odpowiedź na DBA.StackExchange dla TRY / 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:

  1. nie ma indeksu do obsługi zapytania lub
  2. jest indeks, ale co najmniej jedna kolumna w WHERE klauzula jest typem danych typu string, który nie używa sortowania binarnego, stąd COLLATE 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:

  1. Jawnie utwórz #targetIds zamiast używać SELECT INTO...
  2. Dla #targetIds tabeli, zadeklaruj klastrowany klucz podstawowy w kolumnie (kolumnach).
  3. Dla #batchIds tabeli, zadeklaruj klastrowany klucz podstawowy w kolumnie (kolumnach).
  4. Do wstawienia do #targetIds , użyj INSERT 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).



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. IDENTITY() vs IDENTITY() w SQL Server:jaka jest różnica?

  2. In-Memory OLTP:Co nowego w SQL Server 2016

  3. Pomyślnie nawiązano połączenie z serwerem, ale podczas uzgadniania przed logowaniem wystąpił błąd

  4. Wykonaj sp_msforeachdb w aplikacji Java

  5. Jak automatycznie wygenerować unikalny identyfikator w SQL, taki jak UID12345678?