-
Не трябва да актуализирате 10 000 реда в набор, освен ако не сте сигурни, че операцията получава заключване на страници (поради това, че няколко реда на страница са част от
UPDATE
операция). Проблемът е, че ескалацията на заключване (от заключване на ред или страница към таблица) се случва при 5000 ключалки . Така че е най-безопасно да го запазите малко под 5000, само в случай, че операцията използва заключване на редове. -
Не трябва да не да използвате SET ROWCOUNT, за да ограничите броя на редовете, които ще бъдат променени. Тук има два проблема:
-
Това е остаряло от пускането на SQL Server 2005 (преди 11 години):
Използването на SET ROWCOUNT няма да засегне изразите DELETE, INSERT и UPDATE в бъдеща версия на SQL Server. Избягвайте да използвате SET ROWCOUNT с изрази DELETE, INSERT и UPDATE в нова разработка и планирайте да модифицирате приложения, които в момента го използват. За подобно поведение използвайте синтаксиса TOP
-
Това може да засегне не само изявлението, с което работите:
Задаването на опцията SET ROWCOUNT кара повечето Transact-SQL изрази да спрат обработката, когато са били засегнати от посочения брой редове. Това включва тригери. Опцията ROWCOUNT не засяга динамичните курсори, но ограничава набора от редове от набор от ключове и нечувствителни курсори. Тази опция трябва да се използва с повишено внимание.
Вместо това използвайте
TOP ()
клауза. -
-
Тук няма цел да има изрична транзакция. Това усложнява кода и нямате обработка за
ROLLBACK
, което дори не е необходимо, тъй като всяко изявление е собствена транзакция (т.е. auto-commit). -
Ако приемем, че намерите причина да запазите изричната транзакция, тогава нямате
TRY
/CATCH
структура. Моля, вижте моя отговор на DBA.StackExchange заTRY
/CATCH
шаблон, който обработва транзакции:Трябва ли да обработваме транзакция в C# Code, както и в процедурата на Store
Подозирам, че истинският WHERE
клаузата не е показана в примерния код във Въпроса, така че просто разчитайки на показаното, по-добро моделът би бил:
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;
Чрез тестване на @Rows
срещу @BatchSize
, можете да избегнете това окончателно UPDATE
заявка (в повечето случаи), тъй като крайният набор обикновено е известен брой редове, по-малък от @BatchSize
, в който случай знаем, че няма повече за обработка (което виждате в изхода, показан във вашия отговор). Само в случаите, когато крайният набор от редове е равен на @BatchSize
този код ще изпълни ли окончателно UPDATE
засяга 0 реда.
Добавих и условие към WHERE
клауза, за да предотвратите повторно актуализиране на редове, които вече са били актуализирани.
ЗАБЕЛЕЖКА ОТНОСНО ЕФЕКТИВНОСТ
Наблегнах на „по-добро“ по-горе (като „това е по-добро модел"), тъй като това има няколко подобрения спрямо оригиналния код на O.P. и работи добре в много случаи, но не е идеално за всички случаи. За таблици с поне определен размер (който варира поради няколко фактора, така че мога да " Бъдете по-конкретни), производителността ще се влоши, тъй като има по-малко редове за коригиране, ако:
- няма индекс, който да поддържа заявката, или
- има индекс, но поне една колона в
WHERE
клаузата е низов тип данни, който не използва двоично съпоставяне, следователноCOLLATE
Клауза се добавя към заявката тук, за да се наложи двоичното съпоставяне и това прави индекса невалиден (за тази конкретна заявка).
Това е ситуацията, с която се сблъска @mikesigs, което изисква различен подход. Актуализираният метод копира идентификаторите за всички редове, които трябва да бъдат актуализирани във временна таблица, след което използва тази временна таблица за INNER JOIN
към таблицата, която се актуализира в клъстерираната колона(и) на индекса. (Важно е да улавяте и да се присъедините към клъстерирания индекс колони, независимо дали това са колоните с първичен ключ!).
Моля, вижте @mikesigs отговора по-долу за подробности. Подходът, показан в този отговор, е много ефективен модел, който самият аз съм използвал много пъти. Единствените промени, които бих направил са:
- Изрично създайте
#targetIds
таблица, вместо да използватеSELECT INTO...
- За
#targetIds
таблица, декларирайте клъстериран първичен ключ в колоната(ите). - За
#batchIds
таблица, декларирайте клъстериран първичен ключ в колоната(ите). - За вмъкване в
#targetIds
, използвайтеINSERT INTO #targetIds (column_name(s)) SELECT
и премахнетеORDER BY
тъй като не е необходимо.
Така че, ако нямате индекс, който може да се използва за тази операция, и не можете временно да създадете такъв, който действително ще работи (филтриран индекс може да работи, в зависимост от вашия WHERE
клауза за UPDATE
заявка), след което опитайте подхода, показан в отговора на @mikesigs (и ако използвате това решение, моля, гласувайте за него).