Самый быстрый способ обновить 120 миллионов записей - PullRequest
44 голосов
/ 14 сентября 2010

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

Update table
       set int_field = -1;

Я дал ему поработать 5 часов перед отменой.

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

Recovery Model = Simple.
MS SQL Server 2005

Какой-нибудь совет, как сделать это быстрее?

Ответы [ 11 ]

36 голосов
/ 15 сентября 2010

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


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

Для таблицы без кластеризованного индекса в течение времени без одновременного DML:

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

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


Непростой случай

Сначала воссоздайте свой 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

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

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

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

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

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

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

-- -- 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 в отдельную таблицу.Включите этот триггер, прежде чем начать вставку.Затем в той же транзакции, в которой вы выполняете передачу схемы, используйте таблицу журнала для выполнения проверки.Проверьте это сначала на подмножестве данных!Дельты легко испортить.

13 голосов
/ 14 сентября 2010

Если у вас есть место на диске, вы можете использовать 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
9 голосов
/ 14 сентября 2010

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

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
4 голосов
/ 14 сентября 2010

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

5 часов - это много для 120 миллионов записей.

3 голосов
/ 14 сентября 2010
set rowcount 1000000
Update table set int_field = -1 where int_field<>-1

посмотрите, как быстро это происходит, настройте и повторите при необходимости

2 голосов
/ 05 октября 2012
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 @cnt=@cnt+1
  end
2 голосов
/ 15 сентября 2010

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

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

ALTER TABLE MyTable
 drop constraint MyTable_TemporaryDefault

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

2 голосов
/ 14 сентября 2010

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

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

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

1 голос
/ 14 сентября 2010

Звучит как проблема с индексированием, как упомянуто Пабла Санта-Крус. Поскольку ваше обновление не является условным, вы можете УДАЛИТЬ столбец и ЗАПИСАТЬ его со значением ПО УМОЛЧАНИЮ.

0 голосов
/ 14 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...