2010-09-14 3 views
30

Мне нужно инициализировать новое поле со значением -1 в таблице записей 120 миллионов.Самый быстрый способ обновить 120 миллионов записей

Update table 
     set int_field = -1; 

Я отпустил его на 5 часов, прежде чем отменять его.

Я попытался запустить его с уровнем транзакции, установленным для чтения без учета тех же результатов.

Recovery Model = Simple. 
MS SQL Server 2005 

Любые советы по ускорению этой работы?

+3

Проблема основана на IO. Ремус рассмотрел эту проблему здесь: http://stackoverflow.com/questions/3523831/update-statement-running-for-too-long-or-not/3523903#3523903 –

+0

Я был обеспокоен тем, что это может быть основано на IO. К сожалению, я не могу с этим поделать. Будут ли меньшие инкрементные партии работать быстрее? –

+0

Нет. Общий объем данных будет таким же: обновленные страницы и страницы журнала. Это все еще 120M строк, и эти строки эффективно записываются дважды (один раз в журнал и один раз в хранилище резервных копий). Таким образом, время должно быть пропорционально размеру вашего стола. Если это таблица с одним 4 байтом int, это грубые 1G данных для записи (120 * 4 = 480, * 2 - 960). Итак, насколько велика общая таблица? –

ответ

25

Единственный разумный способ обновить таблицу записей 120M - это инструкция SELECT, которая заполняет таблицу второй. Вы должны позаботиться об этом. Инструкции ниже.


Простого случай

Для таблицы ж/из кластерного индекса, в то время, ж/из параллельного DML:

  • SELECT *, new_col = 1 INTO clone.BaseTable FROM dbo.BaseTable
  • воссоздавать индексы, ограничения и т.д. на новый стол
  • переключатель старый и новый w/ALTER SCHEMA ... TRANSFER.
  • падение старого стол

Если вы не можете создать клон схемы, другое имя таблицы в той же схеме будет делать. Не забывайте переименовывать все свои ограничения и триггеры (если применимо) после переключения.


Непростое Case

Во-первых, воссоздать ваш BaseTable с тем же именем в другой схеме, например clone.BaseTable. Использование отдельной схемы упростит процесс переименования позже.

  • Включите в кластерный индекс, если применимо. Помните, что первичные ключи и уникальные ограничения могут быть сгруппированы, но не обязательно так.
  • Включите столбцы идентификации и вычисленные столбцы, если применимо.
  • Включите новый столбец INT, где бы он ни находился.
  • Не включайте любое из следующих действий:
    • запускает
    • внешнего ключа
    • некластеризованные индексы/первичные ключи/уникальные ограничения
    • проверочные ограничения или ограничения, по умолчанию. Значения по умолчанию не имеют большого значения, но мы стараемся сохранить минимальные значения .

Затем проверьте вашу вставку ж/1000 строк:

-- assuming an IDENTITY column in BaseTable 
SET IDENTITY_INSERT clone.BaseTable ON 
GO 
INSERT clone.BaseTable WITH (TABLOCK) (Col1, Col2, Col3) 
SELECT TOP 1000 Col1, Col2, Col3 = -1 
FROM dbo.BaseTable 
GO 
SET IDENTITY_INSERT clone.BaseTable OFF 

Проверьте результаты. Если все кажется в порядке:

  • усечь таблицу Clone
  • убедитесь, что база данных в насыпью протоколированием или простая модель восстановления
  • выполнить полную вставку.

Это займет некоторое время, но не дольше, чем обновление. Как только он завершится, проверьте данные в таблице клонирования, чтобы убедиться, что все правильно.

Затем заново создайте все некластеризованные первичные ключи/уникальные ограничения/индексы и ограничения внешнего ключа (в этом порядке). Восстановите ограничения по умолчанию и проверки, если это применимо. Восстановите все триггеры. Восстановите каждое ограничение, индекс или триггер в отдельной партии. например:

ALTER TABLE clone.BaseTable ADD CONSTRAINT UQ_BaseTable UNIQUE (Col2) 
GO 
-- next constraint/index/trigger definition here 

Наконец, переместите dbo.BaseTable к резервной схеме и clone.BaseTable к схеме ДБО (или там, где ваш стол должен жить).

-- -- perform first true-up operation here, if necessary 
-- EXEC clone.BaseTable_TrueUp 
-- GO 
-- -- create a backup schema, if necessary 
-- CREATE SCHEMA backup_20100914 
-- GO 
BEGIN TRY 
    BEGIN TRANSACTION 
    ALTER SCHEMA backup_20100914 TRANSFER dbo.BaseTable 
    -- -- perform second true-up operation here, if necessary 
    -- EXEC clone.BaseTable_TrueUp 
    ALTER SCHEMA dbo TRANSFER clone.BaseTable 
    COMMIT TRANSACTION 
END TRY 
BEGIN CATCH 
    SELECT ERROR_MESSAGE() -- add more info here if necessary 
    ROLLBACK TRANSACTION 
END CATCH 
GO 

Если вам необходимо высвободить дисковое пространство, вы можете забросьте исходную таблицу в это время, хотя это может быть разумным, чтобы держать его вокруг дольше.

Излишне говорить, что это идеально оффлайн операция. Если у вас есть люди, которые изменяют данные во время выполнения этой операции, вам придется выполнить операцию с использованием переключателя схемы. Я рекомендую создать триггер на dbo.BaseTable, чтобы зарегистрировать весь DML в отдельной таблице. Включите этот триггер перед началом вставки. Затем в той же транзакции, которую вы выполняете для передачи схемы, используйте таблицу журналов для выполнения проверки подлинности. Сначала проверьте это на подмножестве данных! Дельты легко завинчиваются.

3

Если проиндексировано ваше int_field, удалите индекс перед запуском обновления. Затем создайте свой индекс еще раз ...

5 часов, похоже, много за 120 миллионов рекордов.

+4

Что вы получаете при применении изменения данных, вы потеряете (если не хуже) при повторном применении индекса. –

+1

и поле не индексируется. –

2

Что бы я попробовал в первую очередь:
удалить все ограничения, индексы, триггеры и полные текстовые индексы перед обновлением.

Если выше было недостаточно, достаточно было сделать следующий шаг:
для создания файла CSV с 12 миллионами записей и массового импорта с помощью bcp.

Наконец, я бы создал новую таблицу кучи (имея в виду таблицу без первичного ключа) без индексов в другой файловой группе, заполнив ее -1. Разделите старую таблицу и добавьте новый раздел, используя «переключатель».

+0

Почему вы не указали триггеры отключения? –

+0

Спасибо, Денис. Я также упомянул об отключении индексации FTS – Sung

1

Звучит как проблема с индексацией, как упоминалось в Пабло Санта-Крус. Поскольку ваше обновление не является условным, вы можете DROP столбца и RE-ADD его с DEFAULT значением.

+1

Возможно, я что-то делаю не так, но мой опыт работы с настройками по умолчанию заключается в том, что они не установлены при создании столбца, а только при вставке записи. –

+0

Возможно, вы правы (я никогда этого не делал). Возможно, если вы запретили NULLS в столбце, он получит значение из ограничения по умолчанию (поскольку он добавлен в строку, у него будет автогенерированное имя). – Brad

+0

FYI: это не работает. Я пытался. Но хорошая мысль. –

8

Я разбиваю задачу на более мелкие единицы. Протестируйте с различными интервалами размера партии для своей таблицы, пока не найдете оптимальный интервал. Вот пример, который я использовал в прошлом.

declare @counter int 
declare @numOfRecords int 
declare @batchsize int 

set @numOfRecords = (SELECT COUNT(*) AS NumberOfRecords FROM <TABLE> with(nolock)) 
set @counter = 0 
set @batchsize = 2500 

set rowcount @batchsize 
while @counter < (@numOfRecords/@batchsize) +1 
begin 
set @counter = @counter + 1 
Update table set int_field = -1 where int_field <> -1; 
end 
set rowcount 0 
0

В целом, рекомендации являются следующими:

  1. Удалить или просто отключить все индексы, триггеры, ТРУДНОСТИ на столе;
  2. Выполнять COMMIT чаще (например, после каждого 1000 записей, которые были обновлены);
  3. Используйте select ... to.

Но в конкретном случае вы должны выбрать наиболее подходящее решение или их комбинацию.

Также имейте в виду, что когда-нибудь индекс может быть полезным, например. когда вы выполняете обновление неиндексированного столбца некоторым условием.

2
set rowcount 1000000 
Update table set int_field = -1 where int_field<>-1 

видеть, как быстро, что требуется, корректировать и повторять по мере необходимости

+12

'set rowcount' устарел. Вместо этого используйте 'update top (N)'. –

12

Если у вас есть место на диске, вы можете использовать SELECT INTO и создать новую таблицу.Это минимальное протоколирование, так что это будет идти намного быстрее

select t.*, int_field = CAST(-1 as int) 
into mytable_new 
from mytable t 

-- create your indexes and constraints 

GO 

exec sp_rename mytable, mytable_old 
exec sp_rename mytable_new, mytable 

drop table mytable_old 
+0

Да, да. Обновление очень * очень интенсивно. –

+0

@MikeForman Привет, не могли бы вы сообщить мне, как вы могли бы позволить nologging в вышеуказанном решении? –

0

Если таблица имеет индекс, который вы можете перебирать снова я бы поставил update top(10000) заявление в цикле при перемещении над данными. Это позволит сохранить журнал транзакций тонким и не окажет такого огромного влияния на дисковую систему. Кроме того, я бы рекомендовал сыграть с опцией maxdop (установив ее ближе к 1).

2

При добавление нового столбца («инициализировать новое поле») и установив одно значение для каждой существующей строки, я использую следующую тактику:

ALTER TABLE MyTable 
add NewColumn int not null 
    constraint MyTable_TemporaryDefault 
    default -1 

ALTER TABLE MyTable 
drop constraint MyTable_TemporaryDefault 

Если столбец обнуляемый и вы дон 't включить «объявленное» ограничение, для столбца будет установлено значение null для всех строк.

+0

Знаете ли вы, имеет ли это профиль производительности инструкции 'UPDATE', массовый зарегистрированный оператор' INSERT' или что-то еще ? –

+0

Я подозреваю «что-то еще». Я никогда не делал этого на действительно больших столах, но он очень быстро работает * очень быстро на умеренных. Независимо от того, когда вам нужно добавить столбец в существующую таблицу, вы * имеете *, чтобы заплатить цену «все или ничего», так как вы не можете добавить ее только к одной серии строк за раз. (Отражающая сторона INSERT .... SELECT ... для N строк за раз, но если вы делаете это, вам, вероятно, придется запланировать некоторое время простоя, после чего блокировка, блокировка и синхронизация не должны быть довольно критично.) –

+0

Я пробовал это на столе с> 380 миллионами строк, и похоже, что он обновляет таблицу таким же образом и принимает аналогичное количество времени. – Otake

0
declare @cnt bigint 
set @cnt = 1 

while @cnt*100<10000000 
begin 

UPDATE top(100) [Imp].[dbo].[tablename] 
    SET [col1] = xxxx  
WHERE[col2] is null 

    print '@cnt: '+convert(varchar,@cnt) 
    set @[email protected]+1 
    end 
Смежные вопросы