Що стосується методології, я вважаю, що ти забиваєш неправильне b-дерево ;-).
Що ми знаємо:
Спочатку давайте закріпимо та розглянемо те, що ми знаємо про ситуацію:
Що ми можемо припустити:
Далі ми можемо разом переглянути всі ці точки даних, щоб побачити, чи зможемо ми синтезувати додаткові деталі, які допоможуть нам знайти одну або кілька шийок для пляшок, або вказувати на рішення, або принаймні виключати деякі можливі рішення.
Поточний напрямок думки в коментарях полягає в тому, що головна проблема полягає в передачі даних між 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 окремих операцій.
Моя рекомендація (на основі наявної на даний момент інформації):
Вашим найбільшим напрямком удосконалення буде оновлення декількох рядків одночасно (тобто за одну транзакцію). Ви повинні оновити свій процес для роботи в термінах кожного, FileID
а не кожного RowID
. Так:
- читати в усі 4000 рядків певного
FileID
масиву
- масив повинен містити елементи, що представляють маніпулюючі поля
- прокручуйте масив, обробляючи кожен рядок, як ви це робите в даний час
- як тільки всі рядки в масиві (тобто для цього конкретно
FileID
) були обчислені:
- розпочати транзакцію
- зателефонувати кожне оновлення для кожного
RowID
- якщо помилок немає, виконайте транзакцію
- якщо сталася помилка, відкатуйте та обробіть відповідним чином
Якщо ваш кластерний індекс ще не визначений, (FileID, RowID)
слід врахувати це (як @MikaelEriksson запропонував у коментарі до питання). Це не допоможе цим однократним оновленням, але принаймні трохи покращить сукупні операції, наприклад, що ви робите в тій «фільтрованій» збереженій процедурі, оскільки всі вони засновані FileID
.
Вам слід розглянути можливість переміщення логіки до компільованої мови. Я б запропонував створити додаток .NET WinForms або навіть додаток Console. Я віддаю перевагу консольному додатку, оскільки його легко запланувати за допомогою агента SQL або завдань з розкладом Windows. Не має значення, чи робиться це у VB.NET чи C #. VB.NET може бути більш природною для вашого розробника, але все ще буде деяка крива навчання.
На даний момент я не бачу причин переходити до SQLCLR. Якщо алгоритм часто змінюється, то це може набриднути весь час повторно розгортати Асамблею. Перебудова додатка консолі та розміщення .exe у правильній спільній папці в мережі, щоб ви просто запустили ту саму програму, і це завжди буває актуально, це зробити досить просто.
Я не думаю, що повне переміщення обробки в T-SQL не допоможе, якщо проблема полягає в тому, в чому я підозрюю, і ви робите одночасно ОНОВЛЕННЯ.
Якщо обробка переміщена в .NET, ви можете використовувати параметри з табличним значенням (TVP) таким чином, щоб ви перевели масив у збережену процедуру, яка викликала б, UPDATE
що приєднується до змінної таблиці TVP, і, отже, єдина транзакція . TVP повинен бути швидшим, ніж 4000 INSERT
згрупованих в одну транзакцію. Але прибуток від використання ТВП понад 4000 INSERT
с за 1 транзакцію, ймовірно, не буде таким значним, як покращення, що спостерігається при переході від 800 000 окремих транзакцій до лише 200 транзакцій по 4000 рядків кожна.
Варіант TVP не доступний для VBA, але хтось придумав обхід, який, можливо, варто перевірити:
Як покращити продуктивність бази даних при переході від VBA до SQL Server 2008 R2?
ЯКЩО фільтр proc використовується лише FileID
в WHERE
пункті, а якщо ця процедура дійсно викликається в кожному рядку, то ви можете заощадити деякий час обробки, кешуючи результати першого запуску та використовуючи їх для решти рядків у цьому FileID
, правильно?
Після того, як ви отримаєте обробку зроблені в 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 має бути в змозі впоратися з цим) гарантується значно (тобто, за два рази покращення) скоротити загальний час процесу.