Производительность bcp/BULK INSERT по сравнению с табличными параметрами

Мне придется переписать некоторый довольно старый код с помощью команды SQL Server BULK INSERT, потому что схема изменилась, и мне пришло в голову, что, возможно, мне стоит подумать о переходе на хранимую процедуру с помощью TVP, но Мне интересно, какое влияние это может оказать на производительность.

Некоторая справочная информация, которая может помочь объяснить, почему я задаю этот вопрос:

  • Данные фактически поступают через веб-службу. Веб-служба записывает текстовый файл в общую папку на сервере базы данных, которая, в свою очередь, выполняет BULK INSERT. Этот процесс был первоначально реализован на SQL Server 2000, и в то время на самом деле не было никакой альтернативы, кроме как набросать несколько сотен операторов INSERT на сервере, что на самом деле было оригинальным процессом и было катастрофой производительности.

    /li >
  • Данные вставляются в постоянную промежуточную таблицу и затем объединяются в гораздо большую таблицу (после чего она удаляется из промежуточной таблицы).

  • Объем данных для вставки "большой", но не "огромный" - обычно несколько сотен строк, а может быть, 5-10 тыс. строк в редких случаях. Поэтому мое чувство кишки состоит в том, что BULK INSERT, являющееся незарегистрированной операцией, не сделает эту большую разницу (но, конечно, я не уверен, следовательно, вопрос).

  • Вставка фактически является частью гораздо более крупного процесса конвейерной обработки и должна выполняться много раз подряд; поэтому производительность критическая.

Причины, по которым я хотел бы заменить BULK INSERT на TVP:

  • Написание текстового файла через NetBIOS, вероятно, уже стоит некоторое время, и это довольно ужасно с точки зрения архитектуры.

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

  • Я мог бы в значительной степени покончить с проверкой дублирования, кодом очистки и всеми служебными данными, связанными с объемными вставками.

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

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

Итак - для тех, кто достаточно комфортен с SQL Server 2008, чтобы попытаться или хотя бы исследовать это, какой вердикт? Для вставок, скажем, от нескольких сотен до нескольких тысяч строк, происходящих довольно часто, делают ли TVPs горчицу? Есть ли существенная разница в производительности по сравнению с объемными вставками?


Обновление: теперь на 92% меньше вопросительных знаков!

(AKA: результаты теста)

Конечный результат теперь находится в производстве после того, что похоже на 36-этапный процесс развертывания. Оба решения были тщательно протестированы:

  • Вырезать код общей папки и напрямую использовать класс SqlBulkCopy;
  • Переход к хранимой процедуре с TVP.

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

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

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

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

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

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

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

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

    ИЛИ

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

  • Подайте TVP в хранимую процедуру, которая выполняет ту же обработку, что и (7), но напрямую с полученными таблицами. (Законченный)

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

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

Первоначально среднее слияние составляло почти ровно 8 секунд (при нормальной нагрузке). Удаление клика NetBIOS и переход на SqlBulkCopy сократили время почти до 7 секунд. Переход на TVP также сократил время до 5,2 секунды за партию. Это 35% улучшение в производительности для процесса, чье время работы измеряется в часах - настолько неплохо. Это также улучшение на 25% по сравнению с SqlBulkCopy.

Я действительно уверен, что истинное улучшение было значительно больше, чем это. Во время тестирования выяснилось, что окончательное слияние больше не является критическим путем; вместо этого веб-служба, которая выполняла всю обработку данных, начала прятаться под количеством входящих запросов. Ни центральный процессор, ни ввод/вывод базы данных не были полностью отключены, и не было существенной активности блокировки. В некоторых случаях мы наблюдали разрыв нескольких секунд ожидания между последовательными слияниями. При использовании SqlBulkCopy был небольшой зазор, но намного меньше (полсекунды или около того). Но я полагаю, что это станет сказкой на другой день.

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


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

Другими словами, у нас уже есть более чем достаточное понимание процесса и не нужна безопасность промежуточной таблицы; единственная причина, по которой у нас была промежуточная таблица, заключалась в том, чтобы избежать обмана во всех операторах INSERT и UPDATE, которые нам пришлось бы использовать в противном случае. В исходном процессе данные промежуточного этапа в любом случае сохранялись в промежуточной таблице за доли секунды, поэтому она не добавила никакого значения в условиях обслуживания/обслуживания.

Также обратите внимание, что мы заменили не каждую операцию BULK INSERT на TVP. Несколько операций, которые обрабатывают большие объемы данных и/или не должны делать ничего особенного с данными, кроме броска в БД, все еще используют SqlBulkCopy. Я не предлагаю, чтобы TVPs были панацеей производительности, только то, что они преуспели в SqlBulkCopy в этом конкретном экземпляре, включающем несколько преобразований между начальной стадией и окончательным слиянием.

Итак, у вас это есть. Пункт отправляется в TToni для поиска наиболее релевантной ссылки, но я также ценю другие ответы. Еще раз спасибо!

Ответ 1

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

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

Изменить: на боковой ноте вы можете избежать локального файла сервера и по-прежнему использовать массовую копию, используя объект SqlBulkCopy. Просто заполните DataTable и передайте его в "WriteToServer" -метод экземпляра SqlBulkCopy. Прост в использовании и очень быстрый.

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

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