Групування за годиною за великими наборами даних


12

Використовуючи MS SQL 2008, я вибираю усереднене поле з 2,5 мільйонів записів. Кожен запис представляє одну секунду. MyField - це середньомісячна середня кількість записів на 1 секунду. Звичайно серверний процесор досягає 100%, і вибір займає занадто багато часу. Мені потрібно, можливо, зберегти ці усереднені значення, щоб SQL не повинен вибирати всі ці записи в кожному запиті. Що можна зробити?

  SELECT DISTINCT
         CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR)
ORDER BY TimeStamp

6
Чи є TimeStamp частиною кластерного індексу? Це повинно бути ...

@antisanity - чому? він максимізує процесор, а не диск
Джек каже, спробуйте topanswers.xyz

Відповіді:


5

Частина запиту, яка збільшує процесор протягом тривалого періоду, - це функції пункту GROUP BY і той факт, що для цього групування завжди потрібно буде нерозподілений сортування. Хоча індекс у полі часової позначки допоможе початковому фільтру, ця операція повинна бути виконана в кожному рядку, який відповідає фільтру. Прискорення цього використання більш ефективного маршруту, щоб зробити ту саму роботу, як запропонував Алекс, допоможе, але ви все ще маєте величезну неефективність, тому що будь-коли поєднання функцій, яке ви використовуєте, планувальник запитів не зможе придумати щось, що допоможе будь-якому індексу, тому йому доведеться виконувати кожен рядок, спочатку виконуючи функції для обчислення значень групування, лише після цього він може впорядкувати дані та обчислити агрегати за отриманими групуваннями.

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

Ви можете підтримувати додатковий стовпець для кожного рядка, що містить час, округлений до години, та індексувати цей стовпець для використання в таких запитах. Це денормалізує ваші дані, щоб вони могли відчувати себе "брудними", але це спрацювало б і було б чистішим, ніж кешування всіх агрегатів для подальшого використання (і оновлення цього кеша в міру зміни базових даних). Додатковий стовпець повинен підтримуватися тригером або бути збереженим обчисленим стовпцем, а не підтримуватися логікою в іншому місці, оскільки це гарантуватиме всі поточні та майбутні місця, які можуть вставити дані або оновити стовпчики часових позначок або існуючі рядки, привести до послідовних даних у новому стовпчик. Ви все ще можете отримати MIN (часову позначку). Отриманий таким чином запит - це все-таки прогулянка по всіх рядках (очевидно, що цього не уникнути), але це може зробити порядок індексування, виведення рядка для кожного групування, коли він отримує наступне значення в індексі, а не запам'ятовувати весь набір рядків для операції нерозбірливого сортування, перш ніж групування / агрегація може бути виконана. Він також буде використовувати набагато менше пам’яті, оскільки йому не потрібно буде запам’ятовувати будь-які рядки з попередніх значень групування, щоб обробити той, який він зараз дивиться, або решту з них.

Цей метод видаляє необхідність знайти десь у пам'яті для всього набору результатів і зробити невпорядкований сортування для групової операції та видаляє обчислення значень групи з великого запиту (переміщення цього завдання на окремі ВСТАНОВКИ / ОНОВЛЕННЯ, які виробляють даних) і має дозволяти таким запитам запускатися прийнятно, не потребуючи підтримки окремого сховища зведених результатів.

Метод, який ніденормалізувати ваші дані, але все ще потребує додаткової структури, це використовувати "таблицю часу", в цьому випадку такий, що містить один рядок на годину за весь час, який ви, ймовірно, вважаєте. Ця таблиця не займе значної кількості місця в БД або помітного розміру - охоплювати часовий проміжок 100 років таблиці, що містить один ряд з двох дат (початок і кінець години, наприклад, '2011-01-01 @ 00: 00: 00.0000 ',' 2011-01-01 @ 00: 00: 59.9997 ', "9997" є найменшою кількістю мілісекунд, поле DATETIME не округлятиметься до наступної секунди), які є обома частинами кластерний первинний ключ займе ~ 14 Мбайт простору (8 + 8 байт у рядку * 24 години на добу * 365,25 днів / рік * 100, плюс трохи для накладних витрат структури дерев кластеризованого індексу, але цей наклад не буде значним) .

SELECT CONVERT(VARCHAR, [timestamp], 1)+' '+ CAST(DATEPART(Hh,[timestamp]) as VARCHAR) AS TimeStampHour
     , MIN([timestamp]) as TimeStamp
     , AVG(MyField) As AvgField
FROM TimeRangeByHours tt
INNER JOIN MyData md ON md.TimeStamp BETWEEN tt.StartTime AND tt.EndTime
WHERE tt.StartTime > '4/10/2011'
GROUP BY tt.StartTime
ORDER BY tt.StartTime

Це означає, що планувальник запитів може влаштувати індекс на MyData.TimeStamp, який буде використовуватися. Планувальник запитів повинен бути достатньо яскравим, щоб визначити, що він може спускатися по таблиці приборканих кроків з індексом MyData.TimeStamp, знову виводячи один рядок на групування і відкидаючи кожен набір або рядки, коли він потрапляє до наступного значення групування. Не зберігаючи всі проміжні рядки десь у оперативній пам’яті, а потім виконуючи на них нерозроблений сортування Звичайно, цей метод вимагає створити таблицю часу і переконатися, що вона охоплює досить далеко і назад, і вперед, але ви можете використовувати таблицю часу для запитів проти багатьох полів дати в різних запитах, де в якості опції "додатковий стовпець" буде потрібно додатковий обчислюваний стовпець для кожного поля дати, яке потрібно було фільтрувати / згрупувати таким чином, і невеликий розмір таблиці (якщо вам не потрібен проміжок 10,

Метод таблиці часу має додаткову різницю (що може бути досить вигідним) порівняно з вашою поточною ситуацією та рішенням обчислених стовпців: він може повертати рядки за періоди, за які немає даних, просто змінивши INNER JOIN у прикладі запиту вище бути лівим зовнішнім.

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

Побічна примітка: вам також не знадобиться цей пункт DISTINCT для вашого початкового запиту. Групування гарантуватиме, що ці запити повертають лише один рядок за розглянутий період, тож DISTINCT не зробить нічого іншого, крім відкручення процесора трохи більше (якщо планувальник запитів не помітить, що це буде необов'язковим, в цьому випадку він буде ігноруйте його і не використовуйте додатковий час процесора).


3

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

  SELECT DISTINCT
         dateadd(hour,datediff(hour,0,[timestamp]),0) AS TimeStampHour,
         MIN([timestamp]) as TimeStamp,
         AVG(MyField) As AvgField
    FROM MyData
   WHERE TimeStamp > '4/10/2011'
GROUP BY dateadd(hour,datediff(hour,0,[timestamp],0);
ORDER BY TimeStamp

1

Ви хочете зробити запит швидше чи запитуєте, як зробити знімок даних і зберегти його?

Якщо ви хочете зробити це швидше, вам обов'язково потрібен індекс на полі TimeStamp. Також я б запропонував використовувати це для перетворення на годину:

select convert(varchar(13), getdate(), 121)

Якщо вам потрібно зробити знімок і використати його згодом, скористайтеся insert intoдля створення нової таблиці з результатами вашого запиту. Індексуйте таблицю відповідно до та використовуйте її. З того, що я розумію, вам знадобиться індекс на TimeStampHour.

Також ви можете налаштувати завдання, що агрегує щоденні дані у вашій новій сукупній таблиці.


-1

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


1
-1 - ні, ви не "робите це нерозподілене звернення до кожного ряду в базі даних" - будь-який індекс на TimeStampвсе ще буде використовуватися для фільтрації рядків
Джек каже спробувати topanswers.xyz

-3

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

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

  1. Honeywell Uniformance PHD
  2. Osisoft PI
  3. Aspentech IP21
  4. тощо.

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

.. і на загальну примітку: я завжди намагаюся уникати використання DISTINCTключового слова під час написання SQL. Навряд чи це гарна ідея. У вашому випадку ви маєте змогу відмовитися DISTINCTта отримати ті самі результати, додавши MIN([timestamp])до свого GROUP BYпункту.


1
Це не дуже точно. Реляційна база даних ідеально підходить для 2,5 мільйонів записів. І він навіть не приєднується до багатьох столів. Перший показник того, що вам потрібно або денормалізувати свої дані, або перейти на нереляційну систему, - це коли ви робите великі, складні об'єднання в багатьох таблицях. Набір даних плаката насправді звучить як цілком прийнятне використання реляційної системи баз даних.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.