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

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

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

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

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

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

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

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

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

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

  • я могу покончить с Боян-проверка, очистка кода, и все издержки, связанные с массовая вставка.

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

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

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


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

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

конечный результат теперь находится в производстве после того, что кажется 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 kludge и переключение на SqlBulkCopy сократил время почти до 7 секунд. Переход на TVPs еще больше сократил время до 5,2 секунды в пакете. Это улучшение 35% в пропускной способности для процесса, время работы которого измеряется в часах-так что совсем неплохо. Это также улучшение ~25% по сравнению с SqlBulkCopy.

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

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


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

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

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

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

4 ответов


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

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

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


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

вы не упоминаете, используете ли вы .NET, но подход, который я использовал для оптимизации предыдущих решений, состоял в том, чтобы выполнить массовую загрузку данных с помощью SqlBulkCopy класс - вам не нужно писать данные в файл сначала перед загрузкой, просто дайте SqlBulkCopy class (например) A 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 в таблицу temp

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

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

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

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