Скопируйте один столбец в другой для более миллиарда строк в базе данных SQL Server

база данных: SQL Server 2005

: копирование значений из одного столбца в другой столбец в той же таблице с миллиардом+ строки.
test_table (int id, bigint bigid)

вещи попробовали 1: запрос обновления

update test_table set bigid = id 

заполняет журнал транзакций и откатывается из-за отсутствия места в журнале транзакций.

пробовал 2-процедуру по следующим строкам

set nocount on
set rowcount = 500000
while @rowcount > 0
begin
 update test_table set bigid = id where bigid is null
 set @rowcount = @@rowcount
 set @rowupdated = @rowsupdated + @rowcount
end
print @rowsupdated

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

пробовал 3-Создание курсор для обновления.

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

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

любые подсказки, указатели будут много оцененный.

7 ответов


Я собираюсь догадаться, что вы приближаетесь к пределу 2,1 миллиарда типа данных INT на искусственном ключе для столбца. Да, это больно. Гораздо проще исправить перед фактом, чем после того, как вы фактически нажмете этот предел, и производство будет закрыто, пока вы пытаетесь это исправить:)

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

Рост Журнала

журнал взорвался первоначально, потому что он пытался зафиксировать все строки 2b сразу. Предложения в других сообщениях для "chunking it up" будут работать, но это может не полностью решить проблему журнала.

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

индексы и скорости

все where bigid is null ответы будут замедляться по мере заполнения таблицы, потому что (предположительно) нет индекса в новом поле BIGID. Вы могли бы (конечно) просто добавить индекс на BIGID, но я не уверен, что это правильный ответ.

ключ (каламбур) - это мое предположение, что исходное поле ID, вероятно, является первичным ключом или кластеризованный индекс, или оба. В этом случае давайте воспользуемся этим фактом и сделаем вариацию идеи Джесс:

set @counter = 1
while @counter < 2000000000 --or whatever
begin
  update test_table set bigid = id 
  where id between @counter and (@counter + 499999) --BETWEEN is inclusive
  set @counter = @counter + 500000
end

Это должно быть очень быстро, из-за существующих индексов на ID.

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


используйте TOP в инструкция обновления:

UPDATE TOP (@row_limit) dbo.test_table
   SET bigid = id 
 WHERE bigid IS NULL

вы можете попробовать использовать что-то вроде SET ROWCOUNT и пакетные обновления:

SET ROWCOUNT 5000;

UPDATE dbo.test_table 
SET bigid = id 
WHERE bigid IS NULL
GO

и затем повторите это столько раз, сколько вам нужно.

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

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


Это один раз? Если это так, просто сделайте это по диапазонам:

set counter = 500000
while @counter < 2000000000 --or whatever your max id
begin
 update test_table set bigid = id where id between (@counter - 500000) and @counter and bigid is null
 set counter = @counter + 500000
end

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

set rowcount 500000
update test_table tt1
set bigid = (SELECT tt2.id FROM test_table tt2 WHERE tt1.id = tt2.id)
where bigid IS NULL

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

ALTER DATABASE db1
SET RECOVERY SIMPLE
GO

update test_table
set bigid = id
GO

ALTER DATABASE db1
SET RECOVERY FULL
GO

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

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

select id, CAST(id as bigint) bigid into test_table_temp from test_table
drop table test_table
exec sp_rename 'test_table_temp', 'test_table'

Я второй Обновить оператор TOP(X)

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