Я собираюсь переписать какой-то довольно старый код с помощью команды BULK INSERT
SQL Server, потому что схема изменилась, и мне пришло в голову, что, возможно, я должен подумать о переключении на хранимую процедуру с TVP, но Мне интересно, как это может повлиять на производительность.
Некоторая справочная информация, которая может помочь объяснить, почему я задаю этот вопрос:
Данные фактически поступают через веб-сервис. Веб-служба записывает текстовый файл в общую папку на сервере базы данных, которая, в свою очередь, выполняет BULK INSERT
. Этот процесс изначально был реализован на SQL Server 2000, и в то время не было никакой другой альтернативы, кроме как бросить несколько сотен INSERT
операторов на сервере, что на самом деле было первоначальным процессом и привело к падению производительности.
Данные массово вставляются в постоянную промежуточную таблицу, а затем объединяются в таблицу намного большего размера (после чего они удаляются из промежуточной таблицы).
Количество данных для вставки «большое», но не «огромное» - обычно несколько сотен строк, а в редких случаях может быть 5-10 тысяч строк. Поэтому мое внутреннее чувство таково, что BULK INSERT
, будучи незарегистрированной операцией, не будет , что большой разницей (но, конечно, я не уверен, поэтому вопрос).
Вставка на самом деле является частью гораздо более крупного конвейерного пакетного процесса и должна происходить много раз подряд; поэтому производительность является критической.
Причины, по которым я хотел бы заменить BULK INSERT
на TVP:
Запись текстового файла через NetBIOS, вероятно, уже стоит некоторого времени, и это довольно отвратительно с архитектурной точки зрения.
Я считаю, что промежуточный стол можно (и нужно) исключить. Основная причина этого заключается в том, что вставленные данные необходимо использовать для пары других обновлений одновременно с вставкой, и попытка обновления из массивной рабочей таблицы обходится гораздо дороже, чем использование практически пустой промежуточной обработки. Таблица. С TVP параметр в основном равен промежуточной таблице, я могу делать с ней все, что захочу до / после основной вставки.
Я мог бы в значительной степени покончить с проверкой на дублирование, кодом очистки и всеми накладными расходами, связанными с массовыми вставками.
Не нужно беспокоиться о конфликте блокировок на промежуточной таблице или базе данных tempdb, если сервер получает сразу несколько таких транзакций (мы стараемся избегать этого, но это происходит).
Я, очевидно, собираюсь профилировать это перед тем, как что-то вводить в производство, но я подумал, что было бы неплохо сначала спросить, прежде чем я потрачу все это время, чтобы посмотреть, есть ли у кого-нибудь какие-либо строгие предупреждения об использовании TVP для с этой целью.
Итак - для любого, кто достаточно удобен с SQL Server 2008, чтобы попытаться или хотя бы исследовать это, какой вердикт? Для вставок, скажем, от нескольких сотен до нескольких тысяч строк, происходящих на довольно частой основе, TVP режут горчицу? Есть ли существенная разница в производительности по сравнению с объемными вставками?
Обновление: теперь с 92% меньшим количеством вопросительных знаков!
(AKA: результаты испытаний)
Конечный результат теперь в производстве после того, что похоже на 36-этапный процесс развертывания. Оба решения были тщательно протестированы:
- Извлечение кода общей папки и непосредственное использование класса
SqlBulkCopy
;
- Переключение на хранимую процедуру с TVP.
Чтобы читатели могли получить представление о , что именно было проверено , чтобы развеять сомнения относительно надежности этих данных, вот более подробное объяснение того, что на самом деле делает этот процесс импорта
Начните с временной последовательности данных, которая обычно составляет около 20-50 точек данных (хотя иногда она может достигать нескольких сотен);
Выполните целую кучу сумасшедших обработок, которые в основном независимы от базы данных. Этот процесс распараллелен, так что около 8-10 последовательностей в (1) обрабатываются одновременно. Каждый параллельный процесс генерирует 3 дополнительные последовательности.
Возьмите все 3 последовательности и исходную последовательность и объедините их в пакет.
Объедините партии из всех 8-10 завершенных задач обработки в один большой суперпакет.
Импортируйте его, используя стратегию BULK INSERT
(см. Следующий шаг) или стратегию TVP (перейдите к шагу 8).
Используйте класс SqlBulkCopy
, чтобы выгрузить весь суперпакет в 4 постоянных промежуточных стола.
Запустите хранимую процедуру, которая (a) выполняет несколько этапов агрегации для 2 таблиц, включая несколько условий JOIN
, а затем (b) выполняет MERGE
для 6 рабочих таблиц, используя оба агрегированные и неагрегированные данные. (Закончено)
OR
Генерация 4 DataTable
объектов, содержащих данные для объединения; Три из них содержат типы CLR, которые, к сожалению, должным образом не поддерживаются ADO.NET TVP, поэтому их нужно использовать в виде строковых представлений, что немного снижает производительность.
Передать TVP в хранимую процедуру, которая выполняет практически ту же обработку, что и (7), но непосредственно с полученными таблицами. (Закончено)
Результаты были достаточно близки, но в конечном итоге подход TVP в среднем показал лучшие результаты, даже если данные незначительно превысили 1000 строк.
Обратите внимание, что этот процесс импорта выполняется много тысяч раз подряд, поэтому было очень легко получить среднее время, просто посчитав, сколько часов (да, часов) потребовалось, чтобы завершить все слияния.
Первоначально для среднего слияния требовалось почти ровно 8 секунд (при нормальной нагрузке). Удаление клея NetBIOS и переключение на SqlBulkCopy
сократило время почти до 7 секунд. Переключение на TVP еще больше сократило время до 5,2 секунды на пакет. Это 35% улучшение пропускной способности для процесса, время выполнения которого измеряется в часах - так что это совсем неплохо. Это также улучшение на ~ 25% по сравнению с SqlBulkCopy
.
Я на самом деле довольно уверен, что истинное улучшение было значительно больше, чем это. Во время тестирования стало очевидно, что окончательное слияние уже не является критическим путем; вместо этого веб-служба, которая выполняла всю обработку данных, начинала сгибаться в зависимости от числа поступающих запросов. Ни ЦП, ни ввод-вывод базы данных действительно были максимально загружены, и значительных блокировок не было. В некоторых случаях мы видели разрыв в несколько секунд простоя между последовательными слияниями. Был небольшой разрыв, но намного меньше (полсекунды или около того) при использовании SqlBulkCopy
. Но я полагаю, что это станет рассказом для другого дня.
Вывод: Табличные параметры действительно лучше, чем BULK INSERT
операции для сложных процессов импорта + преобразования, работающих с наборами данных среднего размера.
Я хотел бы добавить еще один момент, просто чтобы успокоить любое опасение со стороны людей, которые выступают за промежуточные столы. В некотором смысле, весь этот сервис - один гигантский процесс постановки. Каждый шаг процесса подвергается тщательному аудиту, поэтому нам не требуется промежуточная таблица, чтобы определить причину сбоя какого-либо конкретного слияния (хотя на практике это почти никогда не происходит). Все, что нам нужно сделать, это установить флаг отладки в сервисе, и он сломается для отладчика или выгрузит свои данные в файл вместо базы данных.
Другими словами, мы уже более чем достаточно понимаем процесс и не нуждаемся в безопасности промежуточного стола; единственная причина, по которой у нас была промежуточная таблица, состояла в том, чтобы избежать разбивки на все операторы INSERT
и UPDATE
, которые мы должны были бы использовать в противном случае. В исходном процессе промежуточные данные в любом случае жили в промежуточной таблице только доли секунды, поэтому они не добавляли ценности в плане обслуживания / ремонтопригодности.
Также обратите внимание, что не заменяется на каждую BULK INSERT
операцию с TVP. Некоторые операции, которые имеют дело с большими объемами данных и / или не требуют каких-либо специальных действий с данными, кроме выброса их в БД, все еще используют SqlBulkCopy
. Я не утверждаю, что TVP являются панацеей от производительности, только что они преуспели в SqlBulkCopy
в этом конкретном случае, включающем несколько преобразований между начальной стадией и окончательным объединением.
Так что у вас это есть. Точка отправляется в TToni за поиск наиболее подходящей ссылки, но я также ценю и другие ответы. Еще раз спасибо!