Каковы плюсы и минусы выполнения расчетов в 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

Это зависит от множества факторов, но самое главное:

  • сложность вычислений (предпочитает делать сложный хруст на приложении-сервере, так что масштабы выполненных , а не на сервере БД, которая масштабируется до )
  • объем данных (если вам нужно получить доступ к большому количеству данных или объединить их, выполнение этого на сервере БД позволит сэкономить пропускную способность и диск, если агрегаты можно выполнить внутри индексов)
  • удобство (sql не лучший язык для сложной работы - особенно не очень хорошо для процедурной работы, но очень хорошо для работы на основе множеств; хотя паршивая обработка ошибок)

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

Re ваше примечание:

а затем перебрать записи

Циклический просмотр записей - почти всегда неправильная вещь в sql - написание операции на основе набора является предпочтительным.

Как правило , я предпочитаю свести к минимуму работу базы данных «сохранить эти данные, извлечь эти данные» - однако всегда есть примеры сценариев, когда элегантный запрос на сервере может сэкономить большую пропускную способность.

Также учтите: если это вычислительно дорого, можно ли его где-нибудь кэшировать?

Если вы хотите точное «что лучше»; закодируйте его в обоих направлениях и сравните его (отметив, что первый вариант любого из них, вероятно, не настроен на 100%). Но примите во внимание типичное использование: если в действительности он вызывается 5 раз (отдельно) одновременно, то смоделируйте это: не сравнивайте только одну «1 из этих против 1 из этих».


Зацикливание подразумевает более или менее "построчную" обработку. А это означает, что 2 * задержка сети плюс четыре переключения контекста туда и обратно. Да, это дорого. «Собственная» операция СУБД выполняет всю тяжелую работу по минимизации дисковых операций ввода / вывода (системные вызовы), но позволяет извлечь более одной строки за системный вызов. Строка за раз занимает как минимум четыре системных вызова.
wildplasser

@wildplasser не требуется; сервер может передавать потоковые строки, которые вы потребляете по мере их поступления - метафора «читателя» не редкость.
Марк Гравелл

1
@ Марк Кавелл: Ну, это зависит. В том случае, когда занимаемая область прикладной программы представляет собой только одну логическую запись, это более или менее нормально. Но большинство «фреймворков», которые я знаю, имеют тенденцию всасывать все записи при запуске и запускать их один за другим. Блокировка - еще одна ловушка.
wildplasser

Я думаю, что хорошее эмпирическое правило таково: не возвращайте с сервера SQL те строки данных, которые вам в конечном счете не нужны. Например, если вам нужно выполнить агрегатные операции, они, скорее всего, принадлежат SQL. Объединяет между таблицами или подзапросами? SQL. Это также подход, который мы используем со значками, и до сих пор мы справляемся с масштабом :-)
Sklivvz

1
@zinking, это будет операция на основе множества. В этом сценарии вы не пишете код цикла - это деталь реализации. Под «зацикливанием» я подразумеваю явные циклы, например курсор
Марк Гравелл

86

Позвольте мне использовать метафору: если вы хотите купить золотое ожерелье в Париже, ювелир мог бы сидеть в Кейптауне или Париже, это вопрос мастерства и вкуса. Но вы никогда не отправите тонны золотой руды из Южной Африки во Францию ​​для этого. Руда перерабатывается на месте добычи (или, по крайней мере, в общей зоне), только золото доставляется. То же самое должно быть верно для приложений и баз данных.

Что касается PostgreSQL , вы можете делать на сервере практически все, что угодно. СУБД выделяется при сложных запросах. Для процедурных потребностей вы можете выбрать один из множества языков сценариев на стороне сервера : 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, если есть вероятность, что другим модулям или компонентам в тех же или других проектах потребуется получить эти результаты. атомарная операция, выполняемая на стороне сервера, также лучше, потому что вам просто нужно вызвать сохраненный процесс из любого инструмента управления БД, чтобы получить окончательные значения без дальнейшей обработки.

В некоторых случаях это не относится, но когда это имеет смысл. Кроме того, в целом дБ-бокс имеет лучшее оборудование и производительность.


Возможность повторного использования может присутствовать на любом уровне и не является причиной (с точки зрения производительности) для добавления дополнительных вычислений в SQL. «В общем, блок db»: это неправильно, и, более того, как сказал Марк Гравелл, масштабирование не работает таким же образом. Большинство баз данных требуют небольшого аппаратного обеспечения, чтобы работать прилично, и шаблон производительности не имеет ничего общего с сервером приложений (т.е. я бы потратил 2/3 моего бюджета на сервер SQL на богоподобный ввод-вывод, тогда как я бы не стал тратить больше) чем несколько сотен для стека хранения сервера приложений).
Морг.

1

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

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

В случае OLTP, если вы хотите быть быстрым (10 000–100 000 транзакций в секунду), вы должны удалить конфликты защелок, блокировок и блокировок из базы данных. Это означает, что вам необходимо исключить длинные задержки в транзакциях: циклические переходы от клиента к БД для переноса обработки на клиент являются одним из таких долгих остановок. Вы не можете иметь долгосрочные транзакции (чтобы сделать чтение / обновление атомарными) и иметь очень высокую пропускную способность.

Re: горизонтальное масштабирование. Современные базы данных масштабируются горизонтально. Эти системы уже реализуют HA и отказоустойчивость. Воспользуйтесь этим и постарайтесь упростить пространство своего приложения.

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


0

Выполнять ли вычисления в клиентской части или в серверной части очень решено, можем ли мы определить нашу цель в реализации бизнеса. В то же время Java-код может работать лучше, чем SQL-код, и хорошо написано, или это может быть наоборот. Но все же, если вы запутались, вы можете попытаться определить сначала -

  1. Если вы можете достичь чего-то простого с помощью базы данных sql, тогда лучше пойти на это, так как db будет работать намного лучше и будет выполнять вычисления тут же и тогда с получением результата. Однако, если фактические вычисления требуют слишком больших вычислений, то вы можете пойти с кодом приложения. Зачем? Поскольку сценарии, подобные зацикливанию, в большинстве случаев не лучше всего обрабатываются в SQL, тогда как языки интерфейса лучше разработаны для этих целей.
  2. В случае, если аналогичный расчет требуется во многих местах, тогда очевидно, что размещение кода вычисления в конце БД будет лучше держать вещи в одном месте.
  3. Если для достижения окончательного результата с помощью множества различных запросов необходимо выполнить много вычислений, то также перейдите к db end, поскольку вы можете поместить один и тот же код в хранимую процедуру, чтобы выполнить ее лучше, чем извлекать результаты из бэкэнда, а затем вычислять их на переднем плане. конец.

Есть много других аспектов, о которых вы можете подумать, прежде чем решить, где разместить код. Одно восприятие совершенно неверно - все лучше всего можно сделать в Java (код приложения) и / или все лучше всего сделать с помощью db (sql code).


0

С точки зрения производительности: это очень простая арифметическая операция, которая почти наверняка может быть выполнена намного быстрее, чем фактическая выборка данных с дисков, лежащих в основе базы данных. Кроме того, вычисление значений в предложении where может быть очень быстрым в любой среде выполнения. Таким образом, узким местом должен быть дисковый ввод-вывод, а не вычисление значений.

Что касается читабельности, я думаю, что если вы используете 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. Что лучше привести к центам в запросе? Я не думаю, что приведение к центам добавляет что-либо в ваш запрос.
  2. Лучше ли использовать now () в запросе? Я бы предпочел передавать даты в запрос, а не вычислять их в запросе.

Дополнительная информация: по первому вопросу вы хотите убедиться, что агрегирование дробей работает без ошибок округления. Я думаю, что число 19,2 является разумным для денег, а во втором случае целые числа в порядке. Использование поплавка для денег по этой причине неправильно.

Во втором вопросе мне нравится иметь полный контроль над программистом, какая дата считается «сейчас». При использовании таких функций, как now (), может быть сложно написать автоматические модульные тесты. Кроме того, если у вас более длинный сценарий транзакции, может быть полезно установить переменную, равную now (), и использовать эту переменную так, чтобы вся логика использовала одно и то же значение.


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 секунд Python

0,23801879299935536 секунд SQL

У меня есть 134000 фальшивых записей OHLC в моей базе данных, разделенных на 1000 акций, так что это пример того, где SQL может превзойти ваш сервер приложений


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