Quais são os prós e os contras de executar cálculos em sql vs. em seu aplicativo

154

shopkeeper A tabela possui os seguintes campos:

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

Digamos, eu tenho a tabela acima. Quero obter os registros de ontem e gerar um relatório imprimindo o valor em centavos.

Uma maneira de fazer é realizar cálculos no meu aplicativo java e executar uma consulta simples

Date previousDate ;// $1 calculate in application

Date todayDate;// $2 calculate in application

select amount where createddate between $1 and $2 

e depois percorrer os registros e converter o valor em centavos no meu aplicativo java e gerar o relatório

Outra maneira é como realizar cálculos na própria consulta 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())

e depois percorrer os registros e gerar o relatório

De uma maneira, todo o meu processamento é feito no aplicativo java e uma consulta simples é acionada. Em outro caso, todas as conversões e cálculos são feitos na consulta SQL.

O caso de uso acima é apenas um exemplo, em um cenário real, uma tabela pode ter muitas colunas que requerem processamento do tipo semelhante.

Você pode me dizer qual abordagem é melhor em termos de desempenho e outros aspectos e por quê?

hellojava
fonte
2
Os cálculos de data terão pouco ou nenhum efeito - supondo que seu mecanismo sql realmente calcule suas datas apenas uma vez. tê-los definidos em seu aplicativo faz todo o sentido, pois eles serão definidos em algum momento, seja para o título do relatório ou outras coisas. multiplicar o valor por 100, nesse caso, pode ser feito em qualquer camada, pois você fará um loop nessas linhas de qualquer maneira para renderizar e * 100 provavelmente não será mais lento em qualquer camada, exceto no front-end. Em ambos os casos, seus cálculos são mínimos e diminuídos pelas operações circundantes, não uma preocupação de desempenho.
Morg.

Respostas:

206

Depende de muitos fatores - mas o mais importante:

  • complexidade dos cálculos (preferem fazer trituração complexo em um aplicativo-servidor, uma vez que as escalas de fora , ao invés de um servidor db, que dimensiona -se )
  • volume de dados (se você precisar acessar / agregar muitos dados, fazê-lo no servidor db economizará largura de banda e o disco io se os agregados puderem ser feitos dentro de índices)
  • conveniência (sql não é a melhor linguagem para trabalhos complexos - especialmente não é bom para trabalhos procedurais, mas muito bom para trabalhos baseados em conjuntos; no entanto, um péssimo tratamento de erros)

Como sempre, se você faz trazer a volta de dados para o aplicativo-servidor, minimizando as colunas e linhas será a sua vantagem. Certificar-se de que a consulta esteja ajustada e indexada adequadamente ajudará em ambos os cenários.

Re sua nota:

e depois percorrer os registros

Fazer loop nos registros é quase sempre a coisa errada a se fazer no sql - escrever uma operação baseada em conjunto é o preferido.

Como regra geral , prefiro manter o trabalho do banco de dados no mínimo "armazene esses dados, busque esses dados" - no entanto, sempre existem exemplos de cenários em que uma consulta elegante no servidor pode economizar muita largura de banda.

Considere também: se isso é computacionalmente caro, pode ser armazenado em cache em algum lugar?

Se você quer um "melhor" preciso ; codifique-o nos dois sentidos e compare-o (observando que um primeiro rascunho de um dos dois provavelmente não está 100% sintonizado). Mas considere o uso típico disso: se, na realidade, ele estiver sendo chamado 5 vezes (separadamente) de uma só vez, simule o seguinte: não compare apenas um único "1 destes versus 1 deles".

Marc Gravell
fonte
O loop implica mais ou menos processamento "linha por vez". E isso significa 2 * latência de rede mais quatro comutadores de contexto ida e volta. Sim: isso é caro. Uma operação DBMS "nativa" faz todo o trabalho duro para minimizar as E / S de disco (chamadas do sistema), mas consegue buscar mais de uma linha por chamada do sistema. Linha por vez leva pelo menos quatro chamadas do sistema.
wildplasser
@wildplasser não é necessário; o servidor pode estar transmitindo linhas que você consome à medida que chegam - uma metáfora de "leitor" não é incomum.
Marc Gravell
1
@ Marc Cavell: Bem, isso depende. No caso em que a pegada de um programa aplicativo é apenas um registro lógico, é mais ou menos aceitável. Mas a maioria das "estruturas" que conheço tendem a absorver todos os registros na inicialização e acioná-los, um por um. O bloqueio é outra armadilha.
wildplasser 22/09/11
Penso que uma boa regra geral é: não traga de volta do SQL Server linhas de dados que você não precisa. Por exemplo, se você precisar executar operações agregadas, elas provavelmente pertencem ao SQL. Junta-se entre tabelas ou subconsultas? SQL Essa também é a abordagem que usamos com emblemas, e, até agora, estamos lidando com escala :-)
Sklivvz
1
@zinking que seria uma operação baseada em conjunto. Nesse cenário, você não escreve o código do loop - esse é um detalhe da implementação. Por "looping", quero dizer loops explícitos, por exemplo, um cursor
Marc Gravell
86

Deixe-me usar uma metáfora: se você quiser comprar um colar de ouro em Paris, o ourives poderia sentar-se na Cidade do Cabo ou Paris, isso é uma questão de habilidade e bom gosto. Mas você nunca enviaria toneladas de minério de ouro da África do Sul para a França para isso. O minério é processado no local de mineração (ou pelo menos na área geral), apenas o ouro é enviado. O mesmo deve ser verdade para aplicativos e bancos de dados.

No que diz respeito ao PostgreSQL , você pode fazer quase tudo no servidor, com bastante eficiência. O RDBMS é excelente em consultas complexas. Para necessidades processuais, você pode escolher entre uma variedade de linguagens de script do lado do servidor : tcl, python, perl e muito mais. Principalmente eu uso PL / pgSQL , no entanto.

O pior cenário seria ir repetidamente ao servidor para todas as linhas de um conjunto maior. (Seria como transportar uma tonelada de minério por vez.)

Segundo na fila , se você enviar uma cascata de consultas, cada uma dependendo da anterior, enquanto tudo isso poderá ser feito em uma consulta ou procedimento no servidor. (É como enviar o ouro e cada uma das jóias com um navio separado, sequencialmente.)

Ir e voltar entre o aplicativo e o servidor é caro. Para servidor e cliente. Tente reduzir isso e você ganhará: os procedimentos do servidor e / ou SQL sofisticado, quando necessário.

Acabamos de terminar um projeto em que empacotamos quase todas as consultas complexas nas funções do Postgres. O aplicativo entrega parâmetros e obtém os conjuntos de dados necessários. Rápido, limpo, simples (para o desenvolvedor de aplicativos), a E / S reduziu ao mínimo ... um colar brilhante com uma pegada de baixo carbono.

Erwin Brandstetter
fonte
12
Eu seria cauteloso ao usar essa analogia para tomar decisões de design significativamente com outros desenvolvedores. As analogias são mais um dispositivo retórico do que lógico. Entre outros fatores, é muito mais barato enviar dados para um servidor de aplicativos do que enviar minério de ouro para um ourives.
Doug
3
Você enviará minérios ou ouro, dependendo do que for mais barato, se não tiver tecnologia para converter minério em ouro ou for caro (porque os mineiros querem matar esses outros trabalhadores), você o enviará para outro local, talvez em entre ourives e mineiros, especialmente se você tiver mais de um ourives.
Dainius
1
exatamente o que eu concordo, não acho que seja sempre ruim fazer cálculos baseados em loop no SQL @a_horse_with_no_name; em algum momento isso precisa ser feito de qualquer maneira, prefiro que seja calculado quando os dados forem buscados conforme indicado pela metáfora de Erwin. ou você deve repetir isso a um custo quando os dados forem recuperados.
zinking 8/09/13
-1 Por ser um argumento unilateral, ignora as compensações e cria um homem de palha para o lado oposto, em vez de considerar e refutar o melhor caso do lado oposto. "Ir e voltar entre o aplicativo e o servidor é caro" - absolutamente: mas não é a única coisa cara, e as várias despesas devem ser pesadas uma contra a outra. Pode acontecer que consultas "sofisticadas em SQL" ou procedimentos armazenados sejam os melhores para o caso específico; mas os detalhes do caso geralmente devem ser levados em consideração ao fazer esse tipo de determinação.
yfeldblum 8/09/13
Legal analogia, mas infelizmente é baseada em suposições erradas. O envio de minério de ouro é muito comum. A taxa de extração de ouro é de cerca de 1: 1 (ouro para desperdício), no entanto, geralmente é mais barato processá-lo fora do local, onde estão disponíveis melhores equipamentos e qualidade de fabricação. Dependendo do tamanho da remessa, aumentar a eficiência do processamento em 0,1% pode permitir um aumento relativo da receita (apesar do preço dobrado da remessa) - como o ouro está bastante caro atualmente. Outros minérios, como o ferro, por exemplo, também costumam ser enviados (a taxa de decapagem do ferro é de cerca de 60%!).
precisa
18

Nesse caso, você provavelmente está um pouco melhor fazendo o cálculo no SQL, pois o mecanismo do banco de dados provavelmente possui rotinas aritméticas decimais mais eficientes do que o Java.

Geralmente, porém, para cálculos no nível da linha, não há muita diferença.

O que faz a diferença é:

  • Cálculos agregados como SUM (), AVG (), MIN (), MAX () aqui o mecanismo de banco de dados será uma ordem de magnitude mais rápido que uma implementação Java.
  • Em qualquer lugar o cálculo é usado para filtrar linhas. A filtragem no banco de dados é muito mais eficiente do que ler uma linha e depois descartá-la.
James Anderson
fonte
12

Não há preto / branco com relação a quais partes da lógica de acesso a dados devem ser executadas no SQL e quais partes devem ser executadas em seu aplicativo. Eu gosto da redação de Mark Gravell , distinguindo entre

  • cálculos complexos
  • cálculos intensivos em dados

O poder e a expressividade do SQL são muito subestimados. Desde a introdução das funções da janela , muitos cálculos não estritamente orientados a conjuntos podem ser executados com muita facilidade e elegância no banco de dados.

Três regras práticas devem sempre ser seguidas, independentemente da arquitetura geral do aplicativo:

  • reduza a quantidade de dados transferidos entre o banco de dados e o aplicativo (em favor do cálculo de coisas no banco de dados)
  • reduza a quantidade de dados carregados do disco pelo banco de dados (em favor de permitir que o banco de dados otimize as instruções para evitar acesso desnecessário aos dados)
  • não leve o banco de dados aos limites da CPU com cálculos complexos e simultâneos (em favor de extrair dados para a memória do aplicativo e executar cálculos)

Na minha experiência, com um DBA decente e algum conhecimento decente sobre seu banco de dados decente, você não encontrará os limites de CPU dos seus DBs em breve.

Algumas leituras adicionais onde estas coisas são explicadas:

Lukas Eder
fonte
2

Em geral, faça coisas no SQL se houver chances de que também outros módulos ou componentes no mesmo ou em outros projetos precisem obter esses resultados. uma operação atômica realizada do lado do servidor também é melhor porque você só precisa chamar o processo armazenado de qualquer ferramenta de gerenciamento de banco de dados para obter valores finais sem processamento adicional.

Em alguns casos, isso não se aplica, mas quando isso faz sentido. também, em geral, a caixa db tem os melhores desempenhos e hardware.

Davide Piras
fonte
A reutilização pode estar presente em qualquer camada e não é um motivo (desempenho) para colocar mais cálculos no SQL. "Em geral, a caixa db": isso está errado e, além disso, como disse marc gravell, o dimensionamento não funciona da mesma maneira. A maioria dos bancos de dados exige que pouco hardware seja executado decentemente, e o padrão de desempenho tem pouco a ver com o de um servidor de aplicativos (ou seja, eu gastaria 2/3 do meu orçamento em um servidor SQL em E / S deuses, enquanto eu não gastaria mais algumas centenas para a pilha de armazenamento de um servidor de aplicativos).
Morg.
1

Se você estiver escrevendo sobre ORM ou escrevendo aplicativos casuais de baixo desempenho, use qualquer padrão que simplifique o aplicativo. Se você estiver escrevendo um aplicativo de alto desempenho e pensando cuidadosamente sobre a escala, vencerá movendo o processamento para os dados. Eu defendo fortemente a mudança do processamento para os dados.

Vamos pensar sobre isso em duas etapas: (1) transações OLTP (pequeno número de registros). (2) OLAP (varreduras longas de muitos registros).

No caso OLTP, se você quiser ser rápido (transações de 10 a 100k por segundo), deverá remover a contenção de trava, bloqueio e bloqueio morto do banco de dados. Isso significa que você precisa eliminar longas paradas nas transações: as viagens de ida e volta do cliente para o DB para mover o processamento para o cliente são uma dessas longas paradas. Você não pode ter transações de longa duração (para tornar a leitura / atualização atômica) e ter uma taxa de transferência muito alta.

Re: escala horizontal. Os bancos de dados modernos são dimensionados horizontalmente. Esses sistemas já implementam tolerância a HA e falhas. Aproveite isso e tente simplificar o espaço do aplicativo.

Vejamos o OLAP - nesse caso, deve ser óbvio que arrastar possivelmente terrabytes de dados de volta para o aplicativo é uma ideia horrível. Esses sistemas são construídos especificamente para operar de maneira extremamente eficiente com dados colunares compactados e pré-organizados. Os sistemas OLAP modernos também são dimensionados horizontalmente e possuem planejadores de consultas sofisticados que dispersam o trabalho horizontalmente (movendo internamente o processamento para os dados).

Ryan
fonte
0

A decisão de realizar cálculos no front-end ou no back-end é muito decidida se podemos determinar nosso objetivo na implementação do negócio. No momento, o código java pode ter um desempenho melhor do que um código sql bem escrito ou pode ser vice-versa. Mas ainda assim, se confuso, você pode tentar determinar primeiro -

  1. Se você pode conseguir algo direto via sql do banco de dados, é melhor seguir em frente, pois o db terá um desempenho muito melhor e fará cálculos lá e, em seguida, com a busca do resultado. No entanto, se o cálculo real exigir muito cálculo daqui e de outras coisas, você poderá usar o código do aplicativo. Por quê? Como o loop de cenário na maioria dos casos não é mais bem tratado pelo sql, pois as linguagens de front-end são melhor projetadas para essas coisas.
  2. Caso seja necessário um cálculo semelhante em muitos locais, é óbvio que colocar o código de cálculo no final do banco de dados manterá as coisas no mesmo local.
  3. Se houver muitos cálculos a serem feitos para obter o resultado final por meio de muitas consultas diferentes, vá também para db end, pois você pode colocar o mesmo código em um procedimento armazenado para ter um desempenho melhor do que recuperar resultados do back-end e computá-los na frente fim.

Existem muitos outros aspectos que você pode pensar antes de decidir onde colocar o código. Uma percepção está totalmente errada - tudo pode ser feito melhor em Java (código do aplicativo) e / ou tudo deve ser feito pelo db (código sql).

Neo
fonte
0

Formar um ponto de vista de desempenho: Essa é uma operação aritmética muito simples que quase certamente pode ser executada muito mais rapidamente do que a busca de dados dos discos subjacentes ao banco de dados. Além disso, é provável que o cálculo dos valores na cláusula where seja muito rápido em qualquer tempo de execução. Em resumo, o gargalo deve ser E / S de disco, não o cálculo dos valores.

De acordo com a legibilidade, acho que se você usa um ORM, deve fazê-lo no ambiente do servidor de aplicativos, porque o ORM permitirá trabalhar com os dados subjacentes com muita facilidade, usando operações baseadas em conjunto. Se você for escrever SQL bruto de qualquer maneira, não há nada errado em fazer a computação lá, seu SQL também pareceria um pouco melhor e mais fácil de ler se formatado corretamente.

Johannes Gehrs
fonte
0

Fundamentalmente, "desempenho" não está definido.

O que mais importa para mim é o tempo do desenvolvedor.

Escreva a consulta SQL. Se estiver muito lento ou o banco de dados se tornar um gargalo, reconsidere. Nesse momento, você poderá comparar as duas abordagens e tomar sua decisão com base em dados reais relevantes para sua configuração (hardware e qualquer pilha em que estiver).

user2757750
fonte
0

Não acredito que as diferenças de desempenho possam ser justificadas sem exemplos e referências específicas, mas tenho outra opinião:

Qual você pode manter melhor? Por exemplo, convém alternar seu front-end de Java para Flash, HTML5, C ++ ou qualquer outra coisa. Um grande número de programas passou por essa mudança ou até existe em mais de um idioma, porque eles precisam trabalhar em vários dispositivos.

Mesmo se você tiver uma camada intermediária adequada (a partir do exemplo dado, parece que não é o caso), essa camada poderá mudar e o JBoss poderá se tornar Ruby / Rails.

Por outro lado, é improvável que você substitua o back-end do SQL por algo que não seja um banco de dados relacional com o SQL e, se o fizer, terá que reescrever o front-end do zero de qualquer maneira, para que o ponto seja discutível.

Minha idéia é que, se você fizer cálculos no banco de dados, será muito mais fácil escrever um segundo front-end ou camada intermediária posteriormente, porque você não precisará reimplementar tudo. Na prática, porém, acho que "onde posso fazer isso com código que as pessoas entenderão" é o fator mais importante.

Kajetan Abt
fonte
Se você mudar de jboss para ruby, é muito provável que você altere db (e precisará adotar esses cálculos de qualquer maneira) e não é tão improvável que possa mudar para algo mais diferente, como nosql.
Dainius
0

Simplificar como responder a isso seria examinar o balanceamento de carga. Você deseja colocar a carga onde tiver mais capacidade (se isso fizer algum sentido). Na maioria dos sistemas, é o servidor SQL que rapidamente se torna um gargalo; portanto, a resposta provável é que você não deseja que o SQL faça mais um trabalho do que o necessário.

Também na maioria das arquiteturas, são os servidores SQL que compõem o núcleo do sistema e os sistemas externos que são adicionados.

Mas a matemática acima é tão trivial que, a menos que você esteja levando o sistema ao limite, o melhor lugar para colocá-lo é onde você deseja colocá-lo. Se a matemática não fosse trivial, como calcular sin / cos / tan, por exemplo, um cálculo à distância, o esforço pode se tornar não trivial e exigir planejamento e testes cuidadosos.

Donovanr
fonte
0

As outras respostas a esta pergunta são interessantes. Surpreendentemente, ninguém respondeu à sua pergunta. Você está se perguntando:

  1. É melhor transmitir para centavos na consulta? Não acho que o elenco para centavos adicione algo à sua consulta.
  2. É melhor usar now () na consulta? Eu preferiria passar datas para a consulta em vez de calculá-las na consulta.

Mais informações: Para a pergunta um, você deseja ter certeza de que a agregação das frações funciona sem erros de arredondamento. Acho que o numérico 19,2 é razoável por dinheiro e, no segundo caso, os números inteiros estão OK. Usar um carro alegórico por dinheiro está errado por esse motivo.

Para a pergunta dois, eu gosto de ter controle total como programador de que data é considerada "agora". Pode ser difícil escrever testes de unidade automáticos ao usar funções como agora (). Além disso, quando você tem um script de transação mais longo, pode ser bom definir uma variável igual a now () e usar a variável para que toda a lógica use exatamente o mesmo valor.

Chris Schoon
fonte
0

Deixe-me dar um exemplo real para resolver esta questão

Eu precisava calcular uma média móvel ponderada nos meus dados ohlc, tenho cerca de 134000 velas com um símbolo para cada um fazer isso

  1. Opção 1 Faça em Python / Node etc etc
  2. Opção 2 Faça isso no próprio SQL!

Qual é o melhor?

  • Se eu tivesse que fazer isso em Python, essencialmente, teria que buscar todos os registros armazenados, na pior das hipóteses, executar o cálculo e salvar tudo de volta, o que, na minha opinião, é um enorme desperdício de IO
  • A média móvel ponderada muda sempre que você obtém uma nova vela, o que significa que eu estaria fazendo grandes quantidades de IO em intervalos regulares, o que não é uma boa opinião para mim.
  • No SQL, tudo o que tenho a fazer é provavelmente escrever um gatilho que calcule e armazene tudo; portanto, é necessário buscar apenas os valores finais de WMA para cada par de vez em quando e isso é muito mais eficiente

Exigências

  • Se eu tivesse que calcular o WMA para cada vela e armazená-lo, faria no Python
  • Mas como eu só preciso do último valor, o SQL é muito mais rápido que o Python

Para dar algum incentivo, esta é a versão do Python para fazer uma média móvel ponderada

WMA feito através do código

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 através do 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()

Acredite ou não, a consulta é executada mais rapidamente do que a versão Pure Python de fazer uma MÉDIA EM MOVIMENTO PESADO !!! Eu fui passo a passo para escrever essa consulta, então aguente firme e você vai se sair bem

Rapidez

0.42141127300055814 segundos Python

0.23801879299935536 segundos SQL

Eu tenho 134000 registros OHLC falsos no meu banco de dados, divididos entre 1000 ações. Esse é um exemplo de onde o SQL pode superar o servidor de aplicativos

PirateApp
fonte
1
No entanto, se você precisar fazer isso milhões de vezes o mais rápido possível, será muito mais fácil gerar aplicativos python paralelos do que as réplicas db. Até uma certa escala que se inclina mais no SQL é certamente mais rápida / barata, mas, eventualmente, há um ponto de inflexão quando é melhor fazer esse cálculo em seu aplicativo.
Lenny