Які плюси та мінуси виконання обчислень у sql та у вашій програмі


154

shopkeeper таблиця має такі поля:

id (bigint),amount (numeric(19,2)),createddate (timestamp)

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

Один із способів зробити це - виконати обчислення в моєму додатку Java та виконати простий запит

Date previousDate ;// $1 calculate in application

Date todayDate;// $2 calculate in application

select amount where createddate between $1 and $2 

а потім перегляньте записи та конвертуйте суму в центи в моєму додатку Java та генеруйте звіт

Інший спосіб, як виконання обчислень у самому запиті sql:

select cast(amount * 100 as int) as "Cents"
from shopkeeper  where createddate  between date_trunc('day', now()) - interval '1 day'  and  date_trunc('day', now())

а потім перегляньте записи та генеруйте звіт

Одним чином вся моя обробка виконується в додатку java і запускається простий запит. В іншому випадку всі перетворення та обчислення здійснюються в Sql-запиті.

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

Скажіть, будь ласка, який підхід кращий з точки зору продуктивності та інших аспектів і чому?


2
Розрахунки дат майже не матимуть ніякого ефекту - якщо припустимо, що ваш sql двигун дійсно обчислить ваші дати лише один раз. визначення їх у вашій програмі має ідеальний сенс, оскільки вони будуть визначені там у будь-який момент, будь то за заголовком звіту чи іншими речами. множення значення на 100 в цьому випадку може бути здійснено на будь-якому рівні, оскільки ви все одно будете перебирати ці рядки для візуалізації, і * 100 навряд чи буде повільнішим на будь-якому рівні, крім переднього кінця. У будь-якому випадку ваші розрахунки мінімальні та дефіцитні оточуючими операціями, а не занепокоєнням результативності.
Морг.

Відповіді:


206

Це залежить від безлічі факторів - але найважливіше:

  • складність обчислень (за краще робити складний хрест на додатку-сервері, так що масштаби виконаних , а не на сервері БД, яка масштабується до )
  • об'єм даних (якщо вам потрібно отримати доступ / агрегувати велику кількість даних, це робити на сервері db, економить пропускну здатність і диск, якщо агрегати можна робити всередині індексів)
  • зручність (sql - не найкраща мова для складної роботи - особливо це не чудово для процедурних робіт, але дуже добре для роботи на основі задань; однак, помилкова робота з помилками)

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

Перегляньте свою примітку:

а потім прокрутити записи

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

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

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

Якщо ви хочете точно "що краще"; кодуйте його обома способами та порівняйте (зазначивши, що перший чернеток будь-якого, ймовірно, не налагоджений на 100%). Але чинник типового використання до цього: якщо насправді його викликають 5 разів (окремо) одразу, то моделюйте це: не порівнюйте лише жодного "1 з них проти 1 із них".


Цикл означає більш-менш обробку "рядок за часом". А це означає, що 2 * затримка мережі плюс чотири контекстні комутатори в зворотному напрямку. Так: це дорого. "Нативна" операція СУБД робить всю важку роботу, щоб мінімізувати диск-введення / виведення (системні дзвінки), але вдається отримати більше одного рядка за системний виклик. Ряд за раз приймає щонайменше чотири системні дзвінки.
wildplasser

@wildplasser не потрібен; на сервері можуть бути потокові рядки, які ви споживаєте по мірі їх надходження - метафора "читача" - не рідкість.
Marc Gravell

1
@Marc Cavell: Ну, це залежить. У випадку, коли слід прикладної програми є лише одним логічним записом, це більш-менш ОК. Але більшість "фреймворків", які я знаю, як правило, висмоктують усі записи під час запуску та знімають їх по черзі. Блокування - це ще одна помилка.
wildplasser

Я думаю, що хорошим правилом є: не повертайте з рядків SQL-сервера дані, які вам в кінцевому рахунку не потрібні. Наприклад, якщо вам доведеться виконувати сукупні операції, вони, ймовірно, належать до SQL. Приєднується між таблицями або підзапитами? SQL. Це також підхід, який ми використовуємо зі значками, і поки що ми справляємося з масштабом :-)
Sklivvz

1
@zinking, це буде операція на основі набору. У цьому сценарії ви не пишете код циклу - це деталь реалізації. Під петелькою я маю на увазі явні петлі, наприклад курсор
Marc Gravell

86

Дозвольте скористатись метафорою: якщо ви хочете купити золоте намисто в Парижі, золотар міг би посидіти в Кейптауні або Парижі, це питання майстерності та смаку. Але ви б ніколи не доставляли для цього тонни золотої руди з Південної Африки до Франції. Руда переробляється на майданчику видобутку (або принаймні на загальній площі), відвантажується лише золото. Те саме має стосуватися програм і баз даних.

Що стосується PostgreSQL , ви можете зробити майже все, що завгодно, на сервері, досить ефективно. RDBMS досягає складних запитів. Для процедурних потреб ви можете вибрати різні серверні мови скриптів : tcl, python, perl та багато іншого. В основному я використовую PL / pgSQL , хоча.

Найгіршим сценарієм було б кілька разів переходити на сервер для кожного ряду більшого набору. (Це було б як перевезення однієї тонни руди за раз.)

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

Повернення вперед і назад між додатком та сервером коштує дорого. Для сервера та клієнта. Спробуйте скоротити це, і ви виграєте - ерго: використовуйте серверні процедури та / або складний SQL, де це необхідно.

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


12
Я б з обережністю використовував цю аналогію для прийняття дизайнерських рішень осмислено з іншими розробниками. Аналогії - це скоріше риторичний пристрій, ніж логічний. Серед інших факторів, набагато дешевше перевозити дані на сервер додатків, ніж доставляти золоту руду золотареві.
Даг

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

1
саме те, з чим я згоден, я не думаю, що це завжди погано робити обчислення на основі циклу в SQL @a_horse_with_no_name, коли-небудь це потрібно робити в будь-якому разі, я вважаю за краще, щоб це було обчислено, коли дані отримані як метафора Ервіна. або вам доведеться повторити це ціною, коли дані надійшли назад.
Зінкінг

-1 Оскільки це односторонній аргумент, ігнорує компроміси та встановлює солом’яного чоловіка для протилежної сторони, а не розглядає та спростовує найкращий випадок протилежної сторони. "Повернення вперед і назад між додатком і сервером коштує дорого" - абсолютно: але це не єдине, що дорого, і різні витрати повинні бути зважені один на одного. Може виявитися, що "складні запити SQL" або збережені процедури є найкращими для конкретного випадку; але деталі справи, як правило, повинні враховуватися при визначенні такого роду.
yfeldblum

Класна аналогія, але, на жаль, вона заснована на неправильних припущеннях. Доставка золотих руд дуже поширена. Коефіцієнт зачистки золота становить приблизно 1: 1 (золото для відходів), однак його обробку часто дешевше проводити за межами місця, де доступне краще обладнання та якість виготовлення. Залежно від розміру відвантаження, підвищення ефективності обробки на 0,1% може призвести до відносного збільшення доходу (незважаючи на подвоєну ціну доставки) - оскільки золото є досить дорогим в ці дні. Інші руди, як, наприклад, залізо, зазвичай також поставляються (коефіцієнт зачистки заліза становить близько 60%!).
Кріс Костон

18

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

Взагалі, хоча для обчислень рівня рядків немає великої різниці.

Де це має значення:

  • Сукупні обчислення, такі як SUM (), AVG (), MIN (), MAX (), тут двигун бази даних буде на порядок швидше, ніж реалізація Java.
  • У будь-якому місці розрахунок використовується для фільтрування рядків. Фільтрування в БД набагато ефективніше, ніж читання рядка та його відкидання.

12

Немає чорно-білого щодо того, які частини логіки доступу до даних повинні виконуватися в SQL та які частини слід виконувати у вашій програмі. Мені подобається формулювання Марка Гравелла , що розрізняє

  • складні розрахунки
  • об'ємні дані обчислення

Потужність та виразність SQL сильно занижена. З моменту запровадження віконних функцій у базі даних дуже легко та елегантно можна виконати безліч не строго заданих розрахунків.

Завжди слід дотримуватися трьох правил, незалежно від загальної архітектури додатків:

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

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

Деякі подальші читання, де пояснюються ці речі:


2

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

У деяких випадках це не застосовується, але коли це має сенс. також в цілому db box має найкраще обладнання та продуктивність.


Повторність використання може бути присутнім на будь-якому рівні та не є причиною (для продуктивності) вводити більше обчислень у SQL. "Взагалі коробка db": це неправильно. Крім того, як сказав Марк Гралл, масштабування не працює таким же чином. Більшість баз даних вимагають невеликого обладнання, щоб правильно працювати, і схема продуктивності мало стосується до роботи сервера прикладних програм (тобто я витратив би 2 / 3р. ​​Мого бюджету на SQL-сервер на богоподібний IO, тоді як я б не витрачав більше ніж кілька сотень для стека пам’яті додатка).
Морг.

1

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

Давайте подумаємо про це у два етапи: (1) OLTP (невелика кількість записів) транзакцій. (2) OLAP (довге сканування багатьох записів).

У випадку OLTP, якщо ви хочете бути швидкими (10 к - 100 к транзакцій в секунду), ви повинні вийняти засувку, блокування та суперечки щодо блокування з бази даних. Це означає, що вам потрібно усунути довгі зупинки транзакцій: туди і назад з клієнта до БД, щоб перемістити обробку на клієнта, - це одна така довга стійла. Ви не можете мати довгострокові транзакції (робити читання / оновлення атомними) і мати дуже високу пропускну здатність.

Re: горизонтальне масштабування. Сучасні бази масштабуються горизонтально. Ці системи вже застосовують НА та відмовостійкість. Використовуйте це і намагайтеся спростити простір додатків.

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


0

Вирішувати, чи проводити обчислення на передньому кінці або на початковому етапі, дуже вирішено, чи зможемо ми визначити свою мету в реалізації бізнесу. На час java-код може працювати краще, ніж код sql, як добре написаний, так і може бути навпаки. Але все ж, якщо розгублений, ви можете спробувати визначити спочатку -

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

Є багато інших аспектів, про які можна подумати, перш ніж вирішити, де розмістити код. Одне сприйняття є абсолютно неправильним - все найкраще можна зробити в Java (код програми) та / або все найкраще зробити за допомогою db (sql-коду).


0

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

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


0

Принципово важливо, що "ефективність" не визначена.

Мені найбільше важливо - це час для розробників.

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


0

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

Що ти можеш краще підтримувати? Наприклад, ви можете переключити перехід з Java на Flash, або HTML5, або C ++, або щось інше. Велика кількість програм пережила таку зміну або навіть існує на більш ніж одній мові для початку, оскільки їм потрібно працювати на декількох пристроях.

Навіть якщо у вас належний середній шар (з наведеного прикладу, схоже, що це не так), цей шар може змінитися і JBoss може стати Ruby / Rails.

З іншого боку, навряд чи ви заміните SQL-сервер на щось, що не є реляційним БД, на SQL, і навіть якщо ви це зробите, вам доведеться все-таки переписати фронт-енд з нуля, тому справа не вдається.

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


Якщо ви перейдете з jboss на ruby, велика ймовірність, що ви зміните db (і вам доведеться прийняти ці розрахунки все одно), і це не так вже й малоймовірно, що ви можете змінити щось інше, наприклад nosql.
Даїній

0

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

Крім того, у більшості архітектур ядро ​​системи та зовнішні системи, які додаються, складають саме SQL-сервери.

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


0

Інші відповіді на це питання цікаві. Дивно, але ніхто не відповів на ваше запитання. Вам цікаво:

  1. Чи краще віддати в запит Cents? Я не думаю, що акценти в центрі нічого не додають у ваш запит.
  2. Чи краще використовувати зараз () у запиті? Я вважаю за краще передавати дати в запит, а не обчислювати їх у запиті.

Більше інформації: Для першого питання ви хочете бути впевнені, що агрегація дробів працює без помилок округлення. Я думаю, що число 19,2 є розумним для грошей, а у другому випадку цілі числа - це нормально. Використання плавця за гроші неправильно з цієї причини.

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


0

Дозвольте взяти реальний приклад для вирішення цього питання

Мені потрібно було обчислити середньозважену середню за моїми даними ohlc, у мене є близько 134000 свічок із символом для кожного

  1. Варіант 1 Зробіть це в Python / Node тощо тощо
  2. Варіант 2 Зробіть це в самому SQL!

Який з них кращий?

  • Якби мені довелося це робити в Python, по суті, я повинен був би отримати всі збережені записи в гіршому випадку, виконати обчислення і зберегти все назад, що, на мою думку, є великою витратою IO
  • Середньозважена ковзаюча зміна кожного разу, коли ви отримуєте нову свічку, що означає, що я буду робити велику кількість введення-виводу через рівні проміжки часу, що, на мій знак, не є гарною думкою
  • У SQL все, що мені потрібно зробити, - це, ймовірно, написати тригер, який обчислює і зберігає все, тому потрібно лише раз і потім виводити остаточні значення WMA для кожної пари, і це набагато ефективніше

Вимоги

  • Якби мені довелося розраховувати WMA для кожної свічки і зберігати її, я б робив це на Python
  • Але оскільки мені потрібне лише останнє значення, SQL набагато швидше, ніж Python

Щоб заохотити вас, це версія Python, щоб зробити зважену ковзну середню

WMA зроблено за допомогою коду

import psycopg2
import psycopg2.extras
from talib import func
import timeit
import numpy as np
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute('select distinct symbol from ohlc_900 order by symbol')
for symbol in cur.fetchall():
cur.execute('select c from ohlc_900 where symbol = %s order by ts', symbol)
ohlc = np.array(cur.fetchall(), dtype = ([('c', 'f8')]))
wma = func.WMA(ohlc['c'], 10)
# print(*symbol, wma[-1])
print(timeit.default_timer() - t0)
conn.close()

WMA через SQL

"""
if the period is 10
then we need 9 previous candles or 15 x 9 = 135 mins on the interval department
we also need to start counting at row number - (count in that group - 10)
For example if AAPL had 134 coins and current row number was 125
weight at that row will be weight = 125 - (134 - 10) = 1
10 period WMA calculations
Row no Weight c
125 1
126 2
127 3
128 4
129 5
130 6
131 7
132 8
133 9
134 10
"""
query2 = """
WITH
condition(sym, maxts, cnt) as (
select symbol, max(ts), count(symbol) from ohlc_900 group by symbol
),
cte as (
select symbol, ts,
case when cnt >= 10 and ts >= maxts - interval '135 mins'
then (row_number() over (partition by symbol order by ts) - (cnt - 10)) * c
else null
end as weighted_close
from ohlc_900
INNER JOIN condition
ON symbol = sym
WINDOW
w as (partition by symbol order by ts rows between 9 preceding and current row)
)
select symbol, sum(weighted_close)/55 as wma
from cte
WHERE weighted_close is NOT NULL
GROUP by symbol ORDER BY symbol
"""
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute(query2)
# for i in cur.fetchall():
# print(*i)
print(timeit.default_timer() - t0)
conn.close()

Вірте чи ні, запит запускається швидше, ніж версія Pure Python - робити зважене рухоме середнє значення !!! Я заходив крок за кроком писати цей запит, тож завісьте там, і ви все зробите добре

Швидкість

0.42141127300055814 секунд Пітон

0,23801879299935536 секунд SQL

У моїй базі даних 134000 підроблених записів OHLC, розділених на 1000 запасів, так що це приклад, коли SQL може перевершити ваш сервер додатків


1
Однак, якщо вам потрібно це зробити мільйони разів якомога швидше, це набагато простіше нерестувати паралельні програми python, ніж репліки db. До тих пір, поки певна шкала більше не спирається на SQL, звичайно, швидше / дешевше, але зрештою є переломний момент, коли краще зробити цей розрахунок у вашій програмі.
Ленні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.