sql >> Databáze >  >> RDS >> Sqlserver

Jak aktualizovat velkou tabulku s miliony řádků v SQL Server?

  1. Neměli byste aktualizovat 10 000 řádků v sadě, pokud si nejste jisti, že operace získává uzamčení stránky (kvůli více řádkům na stránce, které jsou součástí UPDATE úkon). Problém je v tom, že k eskalaci uzamčení (buď ze zámků řádků nebo stránek na tabulku) dochází při 5000 zámcích . Nejbezpečnější je tedy držet ji těsně pod 5000, pro případ, že operace využívá Row Locks.

  2. Neměli byste ne pomocí SET ROWCOUNT omezit počet řádků, které budou upraveny. Jsou zde dva problémy:

    1. Od vydání SQL Serveru 2005 (před 11 lety) je tato podpora zastaralá:

      Použití SET ROWCOUNT neovlivní příkazy DELETE, INSERT a UPDATE v budoucí verzi SQL Server. Vyhněte se použití SET ROWCOUNT s příkazy DELETE, INSERT a UPDATE v nových vývojových pracích a plánujte upravit aplikace, které jej aktuálně používají. Pro podobné chování použijte TOP syntaxi

    2. Může to ovlivnit víc než jen prohlášení, se kterým máte co do činění:

      Nastavení možnosti SET ROWCOUNT způsobí, že většina příkazů Transact-SQL zastaví zpracování, pokud byly ovlivněny zadaným počtem řádků. To zahrnuje spouštěče. Možnost POČET ŘÁDEK neovlivňuje dynamické kurzory, ale omezuje sadu řádků sady klíčů a necitlivé kurzory. Tuto možnost je třeba používat opatrně.

    Místo toho použijte TOP () doložka.

  3. Zde nemá smysl mít explicitní transakci. Komplikuje to kód a nemáte žádnou manipulaci s ROLLBACK , což ani není potřeba, protože každý výpis je svou vlastní transakcí (tj. automatickým potvrzením).

  4. Za předpokladu, že najdete důvod pro zachování explicitní transakce, pak nemáte TRY / CATCH struktura. Podívejte se prosím na mou odpověď na DBA.StackExchange pro TRY / CATCH šablona, ​​která zpracovává transakce:

    Jsme povinni zpracovávat transakce v kódu C# i v proceduře Store

Mám podezření, že skutečné WHERE klauzule se v ukázkovém kódu v otázce nezobrazuje, takže se jednoduše spoléhat na to, co bylo ukázáno, je lepší model by byl:

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;

Testováním @Rows proti @BatchSize , můžete se vyhnout poslední UPDATE dotaz (ve většině případů), protože konečná sada je obvykle o určitý počet řádků menší než @BatchSize , v takovém případě víme, že již není co zpracovávat (což vidíte na výstupu zobrazeném ve vaší odpovědi). Pouze v případech, kdy je konečná sada řádků rovna @BatchSize spustí tento kód poslední UPDATE ovlivňující 0 řádků.

Také jsem přidal podmínku do WHERE klauzule, která zabrání opětovné aktualizaci řádků, které již byly aktualizovány.

POZNÁMKA TÝKAJÍCÍ SE VÝKONU

Výše jsem zdůraznil „lepší“ (jako v „toto je lepší model"), protože má několik vylepšení oproti původnímu kódu O.P. a v mnoha případech funguje dobře, ale není dokonalý pro všechny případy. Pro tabulky alespoň určité velikosti (která se liší v důsledku několika faktorů, takže nemohu" t být konkrétnější), výkon se sníží, protože je potřeba opravit méně řádků, pokud:

  1. neexistuje žádný index, který by dotaz podporoval, nebo
  2. existuje index, ale alespoň jeden sloupec v WHERE klauzule je datový typ řetězce, který nepoužívá binární řazení, proto COLLATE Zde je do dotazu přidána klauzule, aby se vynutilo binární řazení, a tím se index (pro tento konkrétní dotaz) zneplatní.

To je situace, se kterou se setkal @mikesigs, což vyžaduje jiný přístup. Aktualizovaná metoda zkopíruje ID všech řádků, které mají být aktualizovány, do dočasné tabulky a poté tuto dočasnou tabulku použije k INNER JOIN do tabulky, která se aktualizuje na sloupcích klíče seskupeného indexu. (Je důležité zachytit shlukovaný index a připojit se k němu bez ohledu na to, zda se jedná o sloupce primárního klíče!).

Podrobnosti najdete v odpovědi @mikesigs níže. Přístup uvedený v této odpovědi je velmi účinným vzorem, který jsem sám použil při mnoha příležitostech. Jediné změny, které bych provedl, jsou:

  1. Explicitně vytvořte #targetIds místo použití SELECT INTO...
  2. Pro #targetIds tabulky, deklarujte seskupený primární klíč ve sloupcích.
  3. Pro #batchIds tabulky, deklarujte seskupený primární klíč ve sloupcích.
  4. Pro vložení do #targetIds , použijte INSERT INTO #targetIds (column_name(s)) SELECT a odeberte ORDER BY protože je to zbytečné.

Pokud tedy nemáte index, který lze pro tuto operaci použít, a nemůžete dočasně vytvořit index, který bude skutečně fungovat (může fungovat filtrovaný index v závislosti na WHERE klauzule pro UPDATE dotaz), pak vyzkoušejte přístup uvedený v odpovědi @mikesigs (a pokud toto řešení používáte, hlasujte pro něj).



  1. Upgrade datové platformy SQL Server v roce 2015

  2. Připojení 32bitové aplikace k jBASE

  3. Může ColumnStore pomoci stránkování?

  4. Jak vybrat první řádek v každé GROUP BY Group