Продуктивність bcp / BULK INSERT порівняно з параметрами, що оцінюються за допомогою таблиці


84

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

Деякі довідкові відомості, які можуть допомогти пояснити, чому я задаю це запитання:

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

  • Дані масово вставляються в постійну проміжну таблицю, а потім об’єднуються у значно більшу таблицю (після чого вони видаляються з проміжної таблиці).

  • Кількість даних, які потрібно вставити, є "великим", але не "величезним" - зазвичай кілька сотень рядків, може бути, 5-10 тис. Вершин рядків у рідкісних випадках. Тому моє відчуття полягає в тому, BULK INSERTщо операція, яка не реєструється, не матиме такої великої різниці (але, звичайно, я не впевнена, звідси питання).

  • Вставка насправді є частиною набагато більшого конвеєрного пакетного процесу, і це має відбуватися багато разів поспіль; тому продуктивність є критичною.

Причини, якими я хотів би замінити BULK INSERTTVP, є:

  • Написання текстового файлу через 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 робочих таблицях, використовуючи як агреговані, так і неагреговані дані. (Готово)

    АБО

  8. Створити 4 DataTableоб’єкти, що містять дані, що об’єднуються; 3 з них містять типи 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 за пошук найбільш релевантного посилання, але я ціную й інші відповіді. Знову дякую!


Це дивовижне питання саме по собі, я вважаю, що частина оновлення повинна бути відповіддю;)
Marc.2377

Відповіді:


10

Я ще насправді не маю досвіду роботи з TVP, однак у MSDN тут є хороша таблиця порівняння продуктивності порівняно з BULK INSERT .

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

Редагувати: у додатковій примітці ви можете уникнути локального файлу на сервері та все одно використовувати масове копіювання за допомогою об’єкта SqlBulkCopy. Просто заповніть таблицю даних і вкажіть її в "WriteToServer" -метод екземпляра SqlBulkCopy. Простий у використанні та дуже швидкий.


Дякую за посилання, це насправді досить корисно, оскільки, схоже, MS рекомендує TVP, коли дані подають складну логіку (що і відбувається), і ми також маємо можливість набирати або зменшувати розмір партії, щоб ми не надто заходили за 1-рядна больова точка. Виходячи з цього, можливо, варто витратити час, принаймні спробувати і подивитися, навіть якщо це в кінцевому підсумку виявиться занадто повільним.
Aaronaught

Так, посилання цікаве. @Aaronaught - у подібних ситуаціях завжди варто вивчити та проаналізувати ефективність потенційних підходів, тому мені було б цікаво почути ваші результати!
AdaTheDev

7

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

З іншого боку, є довідковий документ від групи консультантів з питань обслуговування клієнтів SQL Server: Максимізація пропускної здатності за допомогою TVP

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

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

Щодо тестування, проведеного у Запитанні, я думаю, що це може бути навіть швидшим, ніж те, що було спочатку знайдено:

  1. Ви не повинні використовувати таблицю даних, якщо ваша програма не використовує її за межами надсилання значень до TVP. Використання IEnumerable<SqlDataRecord>інтерфейсу відбувається швидше і використовує менше пам'яті, оскільки ви не дублюєте колекцію в пам’яті лише для того, щоб надіслати її до БД. Я це задокументував у таких місцях:
  2. TVP є табличними змінними і як такі не ведуть статистику. Це означає, що вони повідомляють лише 1 рядок до Оптимізатора запитів. Отже, у вашому proc:
    • Використовуйте перекомпіляцію на рівні оператора для будь-яких запитів, що використовують TVP, для будь-чого іншого, крім простого SELECT: OPTION (RECOMPILE)
    • Створіть локальну тимчасову таблицю (тобто одну #) і скопіюйте вміст TVP у тимчасову таблицю

4

Думаю, я все-таки дотримувався б підходу для масової вставки. Ви можете виявити, що tempdb все ще отримує удар за допомогою TVP з розумною кількістю рядків. Це моє кишкове відчуття, я не можу сказати, що перевірив ефективність використання TVP (мені теж цікаво чути введення інших)

Ви не згадуєте, чи використовуєте .NET, але підхід, який я застосував для оптимізації попередніх рішень, полягав у масовому завантаженні даних за допомогою класу SqlBulkCopy - вам не потрібно спочатку записувати дані у файл завантаження, просто надайте класу SqlBulkCopy (наприклад) DataTable - це найшвидший спосіб вставити дані в БД. 5-10 тис. Рядків - це не багато, я використовував це до 750 тис. Рядків. Я підозрюю, що загалом, з кількома сотнями рядків це не мало би великої різниці за допомогою TVP. Але масштабування буде обмежено IMHO.

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

Крім того, якщо ваша існуюча індексна таблиця є єдиною таблицею, яка використовується для кожного екземпляра цього процесу, і ви турбуєтесь про суперечки тощо, чи не замислювалися ви про створення нової "тимчасової", але фізичної індексної таблиці кожного разу, а потім скидати її, коли це буде закінчили з?

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


Я справді використовую .NET, і процес трапляється раніше SqlBulkCopyі просто ніколи не змінювався. Дякую за нагадування про це, можливо, варто переглянути його. MERGEтакож широко використовується, і тимчасові таблиці вже пробувались раніше, але виявилося, що вони повільніші та складніші в управлінні. Дякуємо за вступ!
Aaronaught

-2

Постановочні столи - це добре! Дійсно, я не хотів би робити це по-іншому. Чому? Оскільки імпорт даних може змінюватися несподівано (І часто способами, яких ви не можете передбачити, як-от час, коли стовпці все ще називались ім’ям та прізвищем, але мали дані про ім’я в стовпці прізвища, наприклад, щоб вибрати приклад не навмання.) Легко дослідити проблему за допомогою проміжної таблиці, щоб ви могли точно бачити, які дані були в стовпцях, оброблених імпортом. Важче знайти, я думаю, коли ви використовуєте таблицю в пам'яті. Я знаю багатьох людей, які займаються імпортом, заробляючи на життя, як і я, і всі вони рекомендують використовувати інсценізаційні столи. Я підозрюю, що для цього є причина.

Подальше закріплення невеликої зміни схеми в робочому процесі простіше і займає менше часу, ніж перепроектування процесу. Якщо він працює, і ніхто не готовий платити годинами, щоб змінити його, тоді виправте лише те, що потрібно виправити через зміну схеми. Змінюючи весь процес, ви вводите набагато більше потенційних нових помилок, ніж вносячи незначні зміни до існуючого, перевіреного робочого процесу.

І як ви збираєтеся покінчити з усіма завданнями з очищення даних? Можливо, ви робите їх інакше, але їх все одно потрібно робити. Знову ж таки, змінювати процес, як ви описуєте, дуже ризиковано.

Мені особисто здається, що вас просто ображає використання старих технік, а не можливість грати з новими іграшками. Ви, схоже, не маєте реальних підстав для бажання змінити, крім оптової вставки - це 2000 рік.


27
SQL 2008 року було навколо в протягом 2 -х років , і цей процес був навколо в протягом століть, і це перший раз , коли я навіть розглянув зміни. Чи був справді необхідним хитрий коментар наприкінці?
Aaronaught
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.