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


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

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

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

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

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

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

причины, по которым я хотел бы заменить BULK INSERT с ТВП являются:

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

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

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

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

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

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


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

(АКА: тест Результаты)

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

  • вырывание кода общей папки и использование SqlBulkCopy класс;
  • переключение на хранимую процедуру с помощью TVPs.

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

  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) выполняет a MERGE на 6 производственных таблицах с использованием как агрегированных, так и неагрегированных данных. (Закончил)

    или

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

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

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

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

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

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


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

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

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

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

4 71

4 ответа:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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