Самый быстрый способ массового обновления
Допустим, у вас есть таблица с примерно 5 миллионами записей и столбец nvarchar(max)
, заполненный большими текстовыми данными. Вы хотите установить этот столбец NULL
, если SomeOtherColumn = 1
самым быстрым способом.
Ломаная сила UPDATE
здесь не работает, потому что она создаст большую неявную транзакцию и займет вечность.
Выполнение обновлений в небольших партиях 50 тыс. записей за один раз работает, но по-прежнему занимает 47 часов, чтобы завершить работу на 32-битном 32-гигабитном сервере.
Есть ли способ сделать это обновление быстрее? Есть ли какие-либо волшебные подсказки/параметры таблицы, которые жертвуют чем-то другим (например, concurrency) в обмен на скорость?
ПРИМЕЧАНИЕ. Создание столбца temp или столбца temp не является опцией, потому что этот столбец nvarchar(max)
содержит много данных и поэтому потребляет много места!
PS: Да, SomeOtherColumn
уже проиндексирован.
Ответы
Ответ 1
Из всего, что я вижу, не похоже, что ваши проблемы связаны с индексами.
Кажется, что ключ состоит в том, что ваше поле nvarchar (max) содержит "много" данных. Подумайте, что SQL должен сделать, чтобы выполнить это обновление.
Поскольку обновляемый столбец, вероятно, содержит более 8000 символов, он хранится за пределами страницы, что подразумевает дополнительные усилия при чтении этого столбца, когда он не равен NULL.
Когда вы запускаете пакет из 50000 обновлений, SQL должен поместить это в неявную транзакцию, чтобы можно было откатить в случае каких-либо проблем. Чтобы откат назад, он должен сохранить исходное значение столбца в журнале транзакций.
Предполагая (для простоты), что каждый столбец содержит в среднем 10 000 байт данных, это означает, что 50 000 строк будут содержать около 500 МБ данных, которые должны храниться временно (в режиме простого восстановления) или постоянно (в режиме полного восстановления).
Невозможно отключить журналы, поскольку это приведет к нарушению целостности базы данных.
Я проверил быстрый тест на моем рабочем столе с медленным рабочим столом, а запущенные партии даже на 10 000 стали непомерно медленными, но при этом размер до 1000 строк, что подразумевает временный размер журнала около 10 МБ, отлично работал.
Я загрузил таблицу с 350 000 строк и отметил 50 000 из них для обновления. Это завершено примерно за 4 минуты, и поскольку он линейно масштабируется, вы сможете обновить все свои 5 миллионов рядов на моем медленном рабочем столе моей собаки примерно за 6 часов на моем 1-процессорном рабочем столе на 2 ГБ, поэтому я бы ожидал чего-то гораздо лучшего на вашем усиленном сервере по SAN или что-то в этом роде.
Возможно, вы захотите запустить свой оператор обновления в качестве выбора, выбрав только первичный ключ и большой столбец nvarchar, и убедитесь, что это выполняется так быстро, как вы ожидаете.
Конечно, узким местом могут быть другие пользователи, блокирующие вещи или конфликты на вашем хранилище или памяти на сервере, но, поскольку вы не упомянули других пользователей, я предполагаю, что для этого у вас есть БД в однопользовательском режиме.
В качестве оптимизации вы должны убедиться, что журналы транзакций находятся на другой группе физических дисков/дисков, чем данные, чтобы минимизировать время поиска.
Ответ 2
Вы можете установить режим восстановления базы данных на Simple
, чтобы уменьшить регистрацию, но не делайте этого, не учитывая все последствия для производственной среды.
Какие индексы установлены на столе? Учитывая, что пакетные обновления ок. 50 000 строк занимают так много времени, я бы сказал, что вам нужен индекс.
Ответ 3
Надеюсь, вы уже сбросили все индексы в столбце, который вы устанавливаете на нуль, включая полные текстовые индексы. Как говорилось ранее, выключение транзакций и файла журнала временно сделало бы трюк. Резервное копирование ваших данных обычно также обрезает ваши файлы журналов.
Ответ 4
Вы пытались помещать индекс или статистику на someOtherColumn?
Ответ 5
Это действительно помогло мне. Я пошел с 2 часов до 20 минут с этим.
/* I'm using database recovery mode to Simple */
/* Update table statistics */
set transaction isolation level read uncommitted
/* Your 50k update, just to have a measures of the time it will take */
set transaction isolation level READ COMMITTED
По моему опыту, работая в MSSQL 2005, каждый день (автоматически) 4 миллиона 46-байтных записей (не nvarchar (max)) из одной таблицы в базе данных в другую таблицу в другой базе данных занимает около 20 минут в сервер QuadCore 8 ГБ, 2 ГГц, и это не повредит производительности приложений. При перемещении я имею в виду INSERT INTO SELECT, а затем DELETE. Использование ЦП никогда не превышает 30%, даже когда удаляемая таблица имеет записи 28M, и она постоянно составляет около 4K в минуту, но обновлений нет. Что ж, это мой случай, он может варьироваться в зависимости от загрузки вашего сервера.
ПРОЧИТАТЬ НЕОБХОДИМЫЕ
"Указывает, что операторы (ваши обновления) могут читать строки, которые были изменены другими транзакциями, но еще не выполнены". В моем случае записи имеют только исходный текст.
Я не знаю, что означает rg-tsql, но здесь вы найдете информацию об уровнях изоляции транзакций в MSSQL.
Ответ 6
Попробуйте индексировать "SomeOtherColumn"... 50K записей должны обновляться. Если уже есть индекс, посмотрите, нужно ли реорганизовать индекс и чтобы были собраны статистические данные.
Ответ 7
Если вы работаете с производственной средой с недостаточным пространством для дублирования всех ваших таблиц, я считаю, что вы рано или поздно ищете проблемы.
Если вы предоставите некоторую информацию о количестве строк с SomeOtherColumn = 1, возможно, мы можем думать по-другому, но я предлагаю:
0) Резервное копирование таблицы
1) Индекс столбца флага
2) Установите для параметра таблицы значение "no log tranctions"... если возможно
3) запишите сохраненную процедуру для запуска обновлений