Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Как да актуализирате голяма таблица с милиони редове в SQL Server?

  1. Не трябва да актуализирате 10 000 реда в набор, освен ако не сте сигурни, че операцията получава заключване на страници (поради това, че няколко реда на страница са част от UPDATE операция). Проблемът е, че ескалацията на заключване (от заключване на ред или страница към таблица) се случва при 5000 ключалки . Така че е най-безопасно да го запазите малко под 5000, само в случай, че операцията използва заключване на редове.

  2. Не трябва да не да използвате SET ROWCOUNT, за да ограничите броя на редовете, които ще бъдат променени. Тук има два проблема:

    1. Това е остаряло от пускането на SQL Server 2005 (преди 11 години):

      Използването на SET ROWCOUNT няма да засегне изразите DELETE, INSERT и UPDATE в бъдеща версия на SQL Server. Избягвайте да използвате SET ROWCOUNT с изрази DELETE, INSERT и UPDATE в нова разработка и планирайте да модифицирате приложения, които в момента го използват. За подобно поведение използвайте синтаксиса TOP

    2. Това може да засегне не само изявлението, с което работите:

      Задаването на опцията SET ROWCOUNT кара повечето Transact-SQL изрази да спрат обработката, когато са били засегнати от посочения брой редове. Това включва тригери. Опцията ROWCOUNT не засяга динамичните курсори, но ограничава набора от редове от набор от ключове и нечувствителни курсори. Тази опция трябва да се използва с повишено внимание.

    Вместо това използвайте TOP () клауза.

  3. Тук няма цел да има изрична транзакция. Това усложнява кода и нямате обработка за ROLLBACK , което дори не е необходимо, тъй като всяко изявление е собствена транзакция (т.е. auto-commit).

  4. Ако приемем, че намерите причина да запазите изричната транзакция, тогава нямате 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. и работи добре в много случаи, но не е идеално за всички случаи. За таблици с поне определен размер (който варира поради няколко фактора, така че мога да " Бъдете по-конкретни), производителността ще се влоши, тъй като има по-малко редове за коригиране, ако:

  1. няма индекс, който да поддържа заявката, или
  2. има индекс, но поне една колона в WHERE клаузата е низов тип данни, който не използва двоично съпоставяне, следователно COLLATE Клауза се добавя към заявката тук, за да се наложи двоичното съпоставяне и това прави индекса невалиден (за тази конкретна заявка).

Това е ситуацията, с която се сблъска @mikesigs, което изисква различен подход. Актуализираният метод копира идентификаторите за всички редове, които трябва да бъдат актуализирани във временна таблица, след което използва тази временна таблица за INNER JOIN към таблицата, която се актуализира в клъстерираната колона(и) на индекса. (Важно е да улавяте и да се присъедините към клъстерирания индекс колони, независимо дали това са колоните с първичен ключ!).

Моля, вижте @mikesigs отговора по-долу за подробности. Подходът, показан в този отговор, е много ефективен модел, който самият аз съм използвал много пъти. Единствените промени, които бих направил са:

  1. Изрично създайте #targetIds таблица, вместо да използвате SELECT INTO...
  2. За #targetIds таблица, декларирайте клъстериран първичен ключ в колоната(ите).
  3. За #batchIds таблица, декларирайте клъстериран първичен ключ в колоната(ите).
  4. За вмъкване в #targetIds , използвайте INSERT INTO #targetIds (column_name(s)) SELECT и премахнете ORDER BY тъй като не е необходимо.

Така че, ако нямате индекс, който може да се използва за тази операция, и не можете временно да създадете такъв, който действително ще работи (филтриран индекс може да работи, в зависимост от вашия WHERE клауза за UPDATE заявка), след което опитайте подхода, показан в отговора на @mikesigs (и ако използвате това решение, моля, гласувайте за него).



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Примери за SQL Server ЗА JSON ПЪТ (T-SQL)

  2. Премахнете и намалете припокриващите се периоди от време

  3. Не може да се свърже със сървъра - грешка, свързана с мрежата или специфична за екземпляр

  4. Използване на SQL Server като DB опашка с множество клиенти

  5. Търсене на обекти от база данни и данни от таблици в SQL Server