-
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. -
Neměli byste ne pomocí SET ROWCOUNT omezit počet řádků, které budou upraveny. Jsou zde dva problémy:
-
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
-
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. -
-
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). -
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 proTRY
/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:
- neexistuje žádný index, který by dotaz podporoval, nebo
- existuje index, ale alespoň jeden sloupec v
WHERE
klauzule je datový typ řetězce, který nepoužívá binární řazení, protoCOLLATE
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:
- Explicitně vytvořte
#targetIds
místo použitíSELECT INTO...
- Pro
#targetIds
tabulky, deklarujte seskupený primární klíč ve sloupcích. - Pro
#batchIds
tabulky, deklarujte seskupený primární klíč ve sloupcích. - Pro vložení do
#targetIds
, použijteINSERT INTO #targetIds (column_name(s)) SELECT
a odeberteORDER 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).