Як реалізувати алгоритм на основі набору / UDF


13

У мене є алгоритм, який мені потрібно протистояти кожному рядку в таблиці з 800K рядками та 38 стовпцями. Алгоритм реалізований у VBA і робить купу математики, використовуючи значення з одних стовпців для маніпулювання іншими стовпцями.

В даний час я використовую Excel (ADO) для запиту SQL і використовую VBA з курсорами на стороні клієнта, щоб застосувати алгоритм циклом через кожен рядок. Працює, але для запуску потрібно 7 годин.

Код VBA є досить складним, щоб переробити його в T-SQL було б багато роботи.

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

В ідеалі я міг би запустити алгоритм проти якомога більшої кількості рядків (усіх?) Паралельним способом.

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

- редагувати

Дякую за коментарі, я використовую MS SQL 2014 Enterprise, ось ще кілька деталей:

Алгоритм знаходить характерні закономірності в даних часових рядів. Функції алгоритму виконують згладжування поліномів, відкривають вікна та знаходять цікаві області на основі вхідних критеріїв, повертаючи десяток значень та деякі булеві результати.

Моє питання стосується скоріше методології, ніж власне алгоритму: Якщо я хочу досягти паралельних обчислень на багатьох рядках одночасно, які мої варіанти.

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

Є T-SQL єдиним способом реалізації заданих функцій?


3
SSIS може запропонувати певну паралелізацію, припускаючи, що ви добре спроектуєте потік даних. Це завдання, яке ви шукаєте, оскільки вам потрібно зробити цей рядок за допомогою обчислення рядків. Однак це означає, що якщо ви не зможете надати конкретні дані (схема, пов'язані з цим розрахунки та що ці розрахунки сподіваються виконати), неможливо допомогти вам оптимізувати. Кажуть, написання речей в зборах може зробити найшвидший код, але якщо, як я, ви жахливо смоктали на це, це зовсім не буде ефективно
billinkc

2
Якщо ви обробляєте кожен рядок самостійно, ви можете розділити 800K рядків на Nпартії та запустити Nекземпляри свого алгоритму на Nокремих процесорах / комп’ютерах. З іншого боку, яке ваше основне вузьке місце - перенесення даних із SQL Server в Excel або фактичні обчислення? Якщо змінити функцію VBA, щоб негайно повернути якийсь фіктивний результат, скільки часу триватиме весь процес? Якщо це все ще займає години, то вузьке місце знаходиться в передачі даних. Якщо це займає секунди, то вам потрібно оптимізувати код VBA, який робить обчислення.
Володимир Баранов

Це фільтр, який викликається як збережена процедура: SELECT AVG([AD_Sensor_Data]) OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) as 'AD_Sensor_Data' FROM [AD_Points] WHERE [FileID] = @FileID ORDER BY [RowID] ASC в студії управління ця функція, яка викликається для кожної з рядків, займає 50mS
medwar19

1
Отже, запит, який займає 50 мс і виконує 800000 разів (11 годин), - це те, що вимагає часу. Чи унікальний @FileID для кожного рядка чи є дублікати, щоб ви могли мінімізувати кількість разів, необхідні для виконання запиту? Ви також можете заздалегідь обчислити середній показник прокрутки для всіх файлів до складової таблиці за один раз (використовувати розділ на FileID), а потім запитувати цю таблицю без необхідності функції вікна для кожного рядка. Найкраща настройка для таблиці постановки виглядає так, як вона має бути з кластерним індексом (FileID, RowID).
Мікаель Ерікссон

1
Найкраще було б, якби ви якось змогли усунути необхідність торкатися db для кожного ряду. Це означає, що вам або доведеться перейти до TSQL і, ймовірно, приєднатися до прошиваючого avg запиту або отримати достатню кількість інформації для кожного рядка, тому все, що потрібно алгоритму, є саме там, у рядку, можливо, закодованим якимось чином, якщо задіяно декілька дочірніх рядків (xml) .
Мікаель Ерікссон

Відповіді:


8

Що стосується методології, я вважаю, що ти забиваєш неправильне b-дерево ;-).

Що ми знаємо:

Спочатку давайте закріпимо та розглянемо те, що ми знаємо про ситуацію:

  • Необхідно виконати дещо складні обчислення:
    • Це має відбуватися в кожному рядку цієї таблиці.
    • Алгоритм часто змінюється.
    • Алгоритм ... [використовує] значення з одних стовпців для маніпулювання іншими стовпцями
    • Поточний час обробки: 7 годин
  • Стіл:
    • містить 800 000 рядків.
    • має 38 стовпців.
  • Застосування програми:
  • База даних - SQL Server 2014, Enterprise Edition.
  • Існує збережена процедура, яка викликається для кожного рядка:

    • На це потрібно 50 мс (приблизно, я вважаю) для запуску.
    • Він повертає приблизно 4000 рядків.
    • Визначення (принаймні частково) таке:

      SELECT AVG([AD_Sensor_Data])
                 OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING)
                 as 'AD_Sensor_Data'
      FROM   [AD_Points]
      WHERE  [FileID] = @FileID
      ORDER BY [RowID] ASC
      

Що ми можемо припустити:

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

Поточний напрямок думки в коментарях полягає в тому, що головна проблема полягає в передачі даних між SQL Server і Excel. Це справді так? Якщо Зберігається процедура викликається для кожного з 800000 рядків і займає 50 мс на кожен дзвінок (тобто на кожен рядок), це додає до 40 000 секунд (не мс). І це еквівалентно 666 хвилин (хммм ;-), або трохи більше 11 годин. Але, як кажуть, на весь процес потрібно лише 7 годин. Ми вже на 4 години за загальний час, і ми навіть додали час, щоб зробити обчислення або зберегти результати назад на SQL Server. Так що тут щось не так.

Дивлячись на визначення збереженої процедури, є лише вхідний параметр для @FileID; немає жодного фільтра @RowID. Тому я підозрюю, що відбувається один із наступних двох сценаріїв:

  • Ця збережена процедура насправді не називається для кожного рядка, а замість кожного @FileID, який, як видається, охоплює приблизно 4000 рядків. Якщо заявлених 4000 рядків повернуто - це цілком однакова кількість, то лише 200 з цих груп у 800 000 рядків. А 200 екзекуцій, що займають 50 мс, становить лише 10 секунд із цих 7 годин.
  • Якщо ця збережена процедура насправді викликається для кожного рядка, то не буде перший раз, коли новий @FileIDбуде взято трохи більше часу, щоб витягнути нові рядки в буферний пул, але тоді наступні 3999 виконання, як правило, повертаються швидше, оскільки вже є кешований, правда?

Я думаю, що зосередження уваги на цьому «фільтрі» Збережена процедура або будь-яка передача даних із SQL Server в Excel - це червона оселедець .

На даний момент я думаю, що найбільш релевантними показниками низької продуктивності є:

  • Є 800 000 рядків
  • Операція працює по одному ряду
  • Дані зберігаються назад на SQL Server, отже, "[використовує] значення з одних стовпців для маніпулювання іншими стовпцями " [мій ем- фаз ;-)]

Я підозрюю, що:

  • в той час, як є можливість покращити пошук даних та обчислення, їх покращення не буде суттєвим скороченням часу обробки.
  • найбільшим вузьким місцем є 800 000 окремих UPDATEзаяв, що становить 800 000 окремих операцій.

Моя рекомендація (на основі наявної на даний момент інформації):

  1. Вашим найбільшим напрямком удосконалення буде оновлення декількох рядків одночасно (тобто за одну транзакцію). Ви повинні оновити свій процес для роботи в термінах кожного, FileIDа не кожного RowID. Так:

    1. читати в усі 4000 рядків певного FileIDмасиву
    2. масив повинен містити елементи, що представляють маніпулюючі поля
    3. прокручуйте масив, обробляючи кожен рядок, як ви це робите в даний час
    4. як тільки всі рядки в масиві (тобто для цього конкретно FileID) були обчислені:
      1. розпочати транзакцію
      2. зателефонувати кожне оновлення для кожного RowID
      3. якщо помилок немає, виконайте транзакцію
      4. якщо сталася помилка, відкатуйте та обробіть відповідним чином
  2. Якщо ваш кластерний індекс ще не визначений, (FileID, RowID)слід врахувати це (як @MikaelEriksson запропонував у коментарі до питання). Це не допоможе цим однократним оновленням, але принаймні трохи покращить сукупні операції, наприклад, що ви робите в тій «фільтрованій» збереженій процедурі, оскільки всі вони засновані FileID.

  3. Вам слід розглянути можливість переміщення логіки до компільованої мови. Я б запропонував створити додаток .NET WinForms або навіть додаток Console. Я віддаю перевагу консольному додатку, оскільки його легко запланувати за допомогою агента SQL або завдань з розкладом Windows. Не має значення, чи робиться це у VB.NET чи C #. VB.NET може бути більш природною для вашого розробника, але все ще буде деяка крива навчання.

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

    Я не думаю, що повне переміщення обробки в T-SQL не допоможе, якщо проблема полягає в тому, в чому я підозрюю, і ви робите одночасно ОНОВЛЕННЯ.

  4. Якщо обробка переміщена в .NET, ви можете використовувати параметри з табличним значенням (TVP) таким чином, щоб ви перевели масив у збережену процедуру, яка викликала б, UPDATEщо приєднується до змінної таблиці TVP, і, отже, єдина транзакція . TVP повинен бути швидшим, ніж 4000 INSERTзгрупованих в одну транзакцію. Але прибуток від використання ТВП понад 4000 INSERTс за 1 транзакцію, ймовірно, не буде таким значним, як покращення, що спостерігається при переході від 800 000 окремих транзакцій до лише 200 транзакцій по 4000 рядків кожна.

    Варіант TVP не доступний для VBA, але хтось придумав обхід, який, можливо, варто перевірити:

    Як покращити продуктивність бази даних при переході від VBA до SQL Server 2008 R2?

  5. ЯКЩО фільтр proc використовується лише FileIDв WHEREпункті, а якщо ця процедура дійсно викликається в кожному рядку, то ви можете заощадити деякий час обробки, кешуючи результати першого запуску та використовуючи їх для решти рядків у цьому FileID, правильно?

  6. Після того, як ви отримаєте обробку зроблені в FILEID , то ми можемо почати говорити про паралельну обробку. Але це може бути не потрібно в цей момент :). Враховуючи, що ви маєте справу з 3 досить важливими неідеальними частинами: транзакціями Excel, VBA та 800k, будь-які розмови про SSIS або паралелограми, або хто-що знає, це передчасна оптимізація / кошик перед конем . Якщо ми можемо зменшити цей 7 годинний процес до 10 хвилин або менше, ви все-таки будете думати про додаткові способи зробити це швидше? Чи є у вас цільовий час завершення? Майте на увазі, що раз обробка проводиться за кожним FileID Якщо ви мали додаток VB.NET Console (тобто командний рядок .EXE), це нічого не заважатиме вам запускати кілька таких файлів FileID одночасно :), будь то через крок SQL Agent CmdExec або Заплановані завдання Windows, тощо.

І, ви завжди можете скористатися "поетапним" підходом і зробити кілька вдосконалень одночасно. Наприклад, почати робити оновлення, FileIDа отже, використовувати одну транзакцію для цієї групи. Потім подивіться, чи можете ви працювати TVP. Потім перегляньте питання про прийняття цього коду та переміщення його до VB.NET (а ТВП працюють у .NET, щоб він міг добре портувати).


Що ми не знаємо, що ще може допомогти:

  • Чи запускається процедура "фільтр", що зберігається, на RowID або на FileID ? Чи є у нас навіть повне визначення цієї збереженої процедури?
  • Повна схема таблиці. Наскільки широка ця таблиця? Скільки полів змінної довжини існує? Скільки полів NULLable? Якщо такі є NULLable, скільки містять NULLs?
  • Індекси для цієї таблиці. Він розділений? Чи використовується стискання ROW або PAGE?
  • Наскільки велика ця таблиця в перерахунку на МБ / ГБ?
  • Як обробляється обслуговування індексу для цієї таблиці? Наскільки фрагментарні показники? Наскільки оновлення на сьогоднішній день є статистикою?
  • Чи пишуть якісь інші процеси до цієї таблиці, поки цей процес триває 7 годин? Можливе джерело суперечок.
  • Чи читаються з цієї таблиці будь-які інші процеси, поки відбувається цей 7-годинний процес? Можливе джерело суперечок.

ОНОВЛЕННЯ 1:

** Мабуть, існує певна плутанина щодо того, що VBA (Visual Basic для додатків) і що з ним можна зробити, тому це просто для того, щоб переконатися, що ми всі на одній веб-сторінці:


ОНОВЛЕННЯ 2:

Ще один момент, який слід врахувати: як обробляються з'єднання? Чи відкривається і закривається з'єднання VBA під час кожної операції, або він відкриває з'єднання на початку процесу і закриває його в кінці процесу (тобто через 7 годин)? Навіть при об'єднанні з'єднань (який за замовчуванням повинен бути включений для ADO), між відкриттям і закриттям одного разу має бути сильний вплив на відміну від відкриття та закриття або 800200, або 1600000 разів. Ці значення ґрунтуються щонайменше на 800 000 ОНОВЛЕННЯ плюс 200 або 800 КВ EXEC (залежно від того, як часто виконується процедура зберігання фільтра).

Ця проблема занадто багато підключень автоматично пом'якшується рекомендацією, яку я виклав вище. Створюючи транзакцію та виконуючи всі ОНОВЛЕННЯ в рамках цієї транзакції, ви будете тримати це з'єднання відкритим і повторно використовувати його для кожної UPDATE. Незалежно від того, чи залишається з'єднання відкритим від початкового виклику, щоб отримати 4000 рядків за вказану FileID, або закрито після цієї операції "дістати" та відкрити знову для UPDATE, набагато менше впливає, оскільки ми зараз говоримо про різницю будь-якого 200 або 400 загальних з'єднань протягом усього процесу.

ОНОВЛЕННЯ 3:

Я зробив кілька швидких тестувань. Майте на увазі, що це досить невеликий масштабний тест, а не точно така ж операція (чистий INSERT vs EXEC + UPDATE). Однак відмінності в термінах, пов’язаних з тим, як обробляються з'єднання та транзакції, як і раніше, є актуальними, отже, інформація може бути екстраполірована і мати тут відносно подібний вплив.

Параметри тесту:

  • Версія для розробників SQL Server 2012 (64-розрядна), SP2
  • Таблиця:

     CREATE TABLE dbo.ManyInserts
     (
        RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
        InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
        SomeValue BIGINT NULL
     );
    
  • Операція:

    INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
  • Загальна кількість вкладок на кожен тест: 10000
  • Скидання кожного тесту: TRUNCATE TABLE dbo.ManyInserts;(враховуючи характер цього тесту, виконання FREEPROCCACHE, FREESYSTEMCACHE та DROPCLEANBUFFERS не здавало великої цінності.)
  • Модель відновлення: ПРОСТО (а може бути, 1 ГБ безкоштовно у файлі журналу)
  • Тести, які використовують транзакції, використовують лише одне підключення незалежно від кількості транзакцій.

Результати:

Test                                   Milliseconds
-------                                ------------
10k INSERTs across 10k Connections     3968 - 4163
10k INSERTs across 1 Connection        3466 - 3654
10k INSERTs across 1 Transaction       1074 - 1086
10k INSERTs across 10 Transactions     1095 - 1169

Як бачимо, навіть якщо з'єднання ADO до БД вже розповсюджується у всіх операціях, групування їх у пакети за допомогою явної транзакції (об’єкт ADO має бути в змозі впоратися з цим) гарантується значно (тобто, за два рази покращення) скоротити загальний час процесу.


Існує хороший підхід "середньої людини" до того, що пропонує srutzky, і це використовувати PowerShell для отримання необхідних даних із SQL Server, викликати свій сценарій VBA для обробки даних, а потім викликати оновлення SP в SQL Server , передаючи ключі та оновлені значення назад на SQL-сервер. Таким чином ви поєднуєте підхід, заснований на наборі, з тим, що у вас вже є.
Стів Манґямелі

@SteveMangiameli Привіт Стів і дякую за коментар. Я відповів би швидше, але захворів. Мені цікаво, як ваша ідея настільки сильно відрізняється від того, що я пропоную. Все свідчить про те, що для запуску VBA все ще потрібен Excel. Або ви припускаєте, що PowerShell замінить ADO, і якщо набагато швидше вводу / виводу, варто було б цього, навіть якщо замінити лише введення / виведення?
Соломон Руцький

1
Не хвилюйтесь, радію, що почуваєте себе краще. Я не знаю, що було б краще. Ми не знаємо, чого не знаємо, і ви зробили чудовий аналіз, але все-таки треба зробити деякі припущення. Введення / виведення може бути досить значним, щоб замінити його самостійно; ми просто не знаємо. Я просто хотів представити ще один підхід, який може бути корисним із запропонованими вами речами.
Стів Манґямелі

@SteveMangiameli Дякую І дякую за роз’яснення цього. Я не був впевнений у вашому точному напрямку та вважав, що краще не припускати. Так, я погоджуюся з тим, що мати більше варіантів краще, оскільки ми не знаємо, які існують обмеження щодо можливих змін :).
Соломон Руцький

Гей серцуцький, дякую за детальні думки! Я знову тестував на стороні SQL, отримуючи оптимізовані індекси та запити та намагався знайти вузькі місця. Зараз я інвестував у належний сервер, 36cores, 1 ТБ позбавив SSI-дисків PCIe, коли IO забився. Тепер виклик коду VB безпосередньо в SSIS, який, як видається, відкриває кілька потоків для паралельних виконання.
medwar19

2

IMHO і працюючи з припущенням, що повторно кодувати підрозділ VBA у SQL не представляється можливим, чи розглядали ви, як дозволити сценарію VBA закінчити оцінку у файлі Excel, а потім записати результати на SQL-сервер через SSIS?

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

Крім того, у вас може бути запущений скрипт VBA програмно (трохи химерно, але я раніше використовував workbook_open()властивість для запуску завдань подібного характеру "запускати та забувати").

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.