Производительность bcp / BULK INSERT по сравнению с табличными параметрами - PullRequest
74 голосов
/ 27 января 2010

Я собираюсь переписать какой-то довольно старый код с помощью команды 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.

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

  1. Начните с временной последовательности данных, которая обычно составляет около 20-50 точек данных (хотя иногда она может достигать нескольких сотен);

  2. Выполните целую кучу сумасшедших обработок, которые в основном независимы от базы данных. Этот процесс распараллелен, так что около 8-10 последовательностей в (1) обрабатываются одновременно. Каждый параллельный процесс генерирует 3 дополнительные последовательности.

  3. Возьмите все 3 последовательности и исходную последовательность и объедините их в пакет.

  4. Объедините партии из всех 8-10 завершенных задач обработки в один большой суперпакет.

  5. Импортируйте его, используя стратегию BULK INSERT (см. Следующий шаг) или стратегию TVP (перейдите к шагу 8).

  6. Используйте класс SqlBulkCopy, чтобы выгрузить весь суперпакет в 4 постоянных промежуточных стола.

  7. Запустите хранимую процедуру, которая (a) выполняет несколько этапов агрегации для 2 таблиц, включая несколько условий JOIN, а затем (b) выполняет MERGE для 6 рабочих таблиц, используя оба агрегированные и неагрегированные данные. (Закончено)

    OR

  8. Генерация 4 DataTable объектов, содержащих данные для объединения; Три из них содержат типы CLR, которые, к сожалению, должным образом не поддерживаются ADO.NET TVP, поэтому их нужно использовать в виде строковых представлений, что немного снижает производительность.

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

Ответы [ 4 ]

8 голосов
/ 27 января 2010

У меня пока нет опыта работы с TVP, но есть хорошая таблица сравнения производительности с BULK INSERT в MSDN здесь .

Говорят, что BULK INSERT имеет более высокую стоимость запуска, но после этого быстрее. В сценарии с удаленным клиентом они рисуют линию около 1000 строк (для «простой» логики сервера). Судя по их описанию, я бы сказал, что у вас должно быть все в порядке с TVP. Падение производительности - если таковое вообще возможно - незначительно, а архитектурные преимущества кажутся очень хорошими.

Редактировать: В примечании вы можете избежать локального файла сервера и все еще использовать массовое копирование, используя объект SqlBulkCopy. Просто заполните DataTable и введите его в метод «WriteToServer» экземпляра SqlBulkCopy. Простой в использовании и очень быстрый.

5 голосов
/ 24 февраля 2015

Таблица, упомянутая в отношении ссылки, приведенной в ответе @ TToni, должна рассматриваться в контексте.Я не уверен, сколько фактического исследования ушло на эти рекомендации (также обратите внимание, что диаграмма, кажется, доступна только в версиях этой документации 2008 и 2008 R2).

С другой стороны, естьэтот технический документ из консультативной группы по SQL Server: Максимизация пропускной способности с TVP

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

Я стараюсь избегать промежуточных таблиц, поскольку проверка данных должна выполняться на уровне приложения.При использовании TVP это легко приспосабливается, и переменная таблицы TVP в хранимой процедуре по своей природе является локализованной промежуточной таблицей (следовательно, нет конфликта с другими процессами, запущенными в то же время, что и при использовании реальной таблицы для промежуточной обработки).

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

  1. Вам не следует использовать DataTable, если тольковаше приложение использует его для отправки значений в TVP.Использование интерфейса IEnumerable<SqlDataRecord> быстрее и использует меньше памяти, поскольку вы не дублируете коллекцию в памяти только для отправки ее в БД.Я задокументировал это в следующих местах:
  2. TVP являются табличными переменными и поэтому не поддерживают статистику.Это означает, что они сообщают оптимизатору запросов только одну строку.Итак, в вашем proc, либо:
    • Используйте перекомпиляцию на уровне операторов в любых запросах с использованием TVP для чего-либо, кроме простого SELECT: OPTION (RECOMPILE)
    • Создайте локальную временную таблицу (т. Е. Одну#) и скопируйте содержимое TVP в временную таблицу
4 голосов
/ 27 января 2010

Я думаю, что я все еще придерживался бы подхода массовой вставки. Вы можете обнаружить, что база данных tempdb все еще используется при использовании TVP с разумным количеством строк. Это мое интуитивное чувство, я не могу сказать, что я тестировал производительность использования TVP (хотя мне интересно услышать мнение других)

Вы не упоминаете, используете ли вы .NET, но подход, который я выбрал для оптимизации предыдущих решений, заключался в том, чтобы выполнить массовую загрузку данных с использованием класса SqlBulkCopy - вам не нужно чтобы сначала записать данные в файл перед загрузкой, просто предоставьте классу SqlBulkCopy (например) DataTable - это самый быстрый способ вставить данные в БД. 5-10K строк не много, я использовал это до 750K строк. Я подозреваю, что в целом, с несколькими сотнями рядов это не будет иметь большого значения при использовании TVP. Но расширение будет ограничено ИМХО.

Возможно, новая функциональность MERGE в SQL 2008 пошла бы вам на пользу?

Кроме того, если ваша существующая промежуточная таблица представляет собой одну таблицу, которая используется для каждого экземпляра этого процесса, и вы беспокоитесь о конфликтах и ​​т. Д., Рассматривали ли вы каждый раз создание новой «временной», но физической промежуточной таблицы, а затем отбрасывание это когда закончится?

Обратите внимание, что вы можете оптимизировать загрузку в эту промежуточную таблицу, заполнив ее без каких-либо индексов. Затем, после заполнения, добавьте все необходимые индексы в этот момент (FILLFACTOR = 100 для оптимальной производительности чтения, так как в этот момент он не будет обновляться).

0 голосов
/ 28 января 2010

Постановочные столы хороши! На самом деле я не хотел бы делать это по-другому. Зачем? Потому что импорт данных может неожиданно измениться (и часто способами, которые вы не можете предвидеть, например, когда столбцы еще назывались именем и фамилией, но имели данные имени в столбце фамилии, например, чтобы выбрать пример не наугад.) Легко исследовать проблему с помощью промежуточной таблицы, чтобы вы могли точно видеть, какие данные были в столбцах, которые обрабатывал импорт. Труднее найти, я думаю, когда вы используете в памяти таблицы. Я знаю многих людей, которые зарабатывают на жизнь, как и я, и все они рекомендуют использовать промежуточные таблицы. Я подозреваю, что для этого есть причина.

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

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

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

...