Quali sono i vantaggi e gli svantaggi di eseguire calcoli in sql vs. nella tua applicazione


154

shopkeeper la tabella ha i seguenti campi:

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

Diciamo, ho la tabella sopra. Voglio ottenere i record per ieri e generare un rapporto facendo stampare l'importo in centesimi.

Un modo di fare è eseguire calcoli nella mia applicazione java ed eseguire una semplice query

Date previousDate ;// $1 calculate in application

Date todayDate;// $2 calculate in application

select amount where createddate between $1 and $2 

e quindi scorrere i record e convertire l'importo in centesimi nella mia applicazione Java e generare il rapporto

Un altro modo è come eseguire calcoli nella query sql stessa:

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 quindi scorrere i record e generare il report

In un certo senso, tutta la mia elaborazione viene eseguita nell'applicazione java e viene generata una semplice query. In altri casi, tutte le conversioni e i calcoli vengono eseguiti nella query SQL.

Il caso d'uso sopra riportato è solo un esempio, in uno scenario reale una tabella può avere molte colonne che richiedono un'elaborazione simile.

Potete per favore dirmi quale approccio è migliore in termini di prestazioni e altri aspetti e perché?


2
I calcoli della data avranno poco o nessun effetto - supponendo che il tuo motore sql calcolerà effettivamente le tue date solo una volta. averli definiti nella tua applicazione ha perfettamente senso, dal momento che saranno comunque definiti lì ad un certo punto, sia per il titolo del rapporto che per altre cose. moltiplicare il valore per 100 in questo caso potrebbe essere fatto su qualsiasi livello, dal momento che eseguirai comunque il loop tra quelle righe per il rendering e * 100 è improbabile che sia più lento su qualsiasi livello tranne il front-end. In entrambi i casi i tuoi calcoli sono minimi e sminuiti dalle operazioni circostanti, non un problema di prestazioni.
Morg.

Risposte:


206

Dipende da molti fattori, ma soprattutto:

  • complessità dei calcoli (preferiscono fare scricchiolio complesso su un app server, dal momento che le scale di fuori , piuttosto che un server db, in grado di scalare fino )
  • volume di dati (se è necessario accedere / aggregare molti dati, farlo sul server db consente di risparmiare larghezza di banda e disco io se gli aggregati possono essere eseguiti all'interno degli indici)
  • convenienza (sql non è la lingua migliore per il lavoro complesso - specialmente non eccezionale per il lavoro procedurale, ma ottimo per il lavoro basato sul set; gestione pessima degli errori, però)

Come sempre, se si fa portare la parte posteriore dei dati per l'applicazione server, riducendo al minimo le colonne e le righe sarà a vostro vantaggio. Assicurarsi che la query sia ottimizzata e indicizzata in modo appropriato aiuterà entrambi gli scenari.

Re la tua nota:

e quindi scorrere tra i record

Il looping dei record è quasi sempre la cosa sbagliata da fare in sql: è preferibile scrivere un'operazione basata su set.

Come regola generale , preferisco mantenere il lavoro del database al minimo "archivia questi dati, recupera questi dati" - tuttavia, ci sono sempre esempi di scenari in cui una query elegante sul server può risparmiare molta larghezza di banda.

Considera anche: se questo è computazionalmente costoso, può essere memorizzato nella cache da qualche parte?

Se vuoi un preciso "che è meglio"; codificalo in entrambi i modi e confrontalo (notando che una prima bozza di uno dei due non è probabilmente sintonizzata al 100%). Ma fattore nell'uso tipico di questo: se, in realtà, viene chiamato 5 volte (separatamente) contemporaneamente, quindi simula: non confrontare solo un singolo "1 di questi contro 1 di quelli".


Il looping implica un'elaborazione più o meno "riga alla volta". Ciò significa latenza di rete 2 * più quattro round switch di contesto. Sì: è costoso. Un'operazione DBMS "nativa" fa tutto il duro lavoro per ridurre al minimo gli I / O del disco (chiamate di sistema) ma riesce a recuperare più di una riga per chiamata di sistema. La riga alla volta richiede almeno quattro chiamate di sistema.
wildplasser,

@wildplasser non necessario; il server potrebbe essere lo streaming di righe che si consumano al loro arrivo - una metafora del "lettore" non è rara.
Marc Gravell

1
@Marc Cavell: Beh, dipende. Nel caso in cui l'impronta di un programma applicativo sia solo un record logico, è più o meno OK. Ma la maggior parte dei "quadri" che conosco tendono a risucchiare tutti i dischi all'avvio e spararli uno per uno. Il blocco è un'altra trappola.
wildplasser,

Penso che una buona regola empirica sia: non riportare da file del server SQL file di dati che in definitiva non sono necessari. Ad esempio, se è necessario eseguire operazioni di aggregazione, probabilmente appartengono a SQL. Unisci tra tabelle o sottoquery? SQL. Questo è anche l'approccio che usiamo con i badge e, finora, stiamo affrontando la scala :-)
Sklivvz,

1
@zinking sarebbe un'operazione basata su set. In quello scenario non scrivi il codice del ciclo - questo è un dettaglio di implementazione. Per "looping" intendo loop espliciti, ad esempio un cursore
Marc Gravell

86

Fammi usare una metafora: se vuoi comprare una collana d'oro a Parigi, orafo potrebbe sedere a Città del Capo o Parigi, è una questione di abilità e gusto. Ma non spediresti mai tonnellate di minerale d'oro dal Sudafrica alla Francia per quello. Il minerale viene elaborato nel sito di estrazione (o almeno nell'area generale), solo l'oro viene spedito. Lo stesso dovrebbe valere per app e database.

Per quanto riguarda PostgreSQL , puoi fare quasi tutto sul server, in modo abbastanza efficiente. RDBMS eccelle nelle query complesse. Per esigenze procedurali è possibile scegliere tra una varietà di linguaggi di script lato server : tcl, python, perl e molti altri. Principalmente utilizzo PL / pgSQL , comunque.

Lo scenario peggiore sarebbe quello di andare ripetutamente al server per ogni singola riga di un set più grande. (Sarebbe come spedire una tonnellata di minerale alla volta.)

Secondo in linea , se si invia una cascata di query, ognuna a seconda di quella precedente, mentre tutto ciò potrebbe essere eseguito in una query o procedura sul server. (È come spedire l'oro e ciascuno dei gioielli con una nave separata, in sequenza.)

Andare avanti e indietro tra app e server è costoso. Per server e client. Prova a ridurlo e vincerai - ergo: usa le procedure lato server e / o SQL sofisticato dove necessario.

Abbiamo appena finito un progetto in cui abbiamo inserito quasi tutte le query complesse nelle funzioni di Postgres. L'app consegna i parametri e ottiene i set di dati di cui ha bisogno. Veloce, pulito, semplice (per lo sviluppatore dell'app), I / O ridotto al minimo ... una collana lucida a basso impatto ambientale.


12
Sarei cauto nell'usare questa analogia per prendere decisioni di progettazione significative con altri sviluppatori. Le analogie sono più un dispositivo retorico che uno logico. Tra gli altri fattori, è molto più economico spedire i dati a un server di app piuttosto che spedire minerale d'oro a un orafo.
Doug,

3
Invierai minerali o oro a seconda di ciò che è più economico, se non hai la tecnologia per convertire il minerale in oro o è costoso (perché i minatori vogliono uccidere questi altri lavoratori), lo spedirai in un'altra posizione, magari in tra orafo e minatori, soprattutto se hai più di un orafo.
Dainius,

1
esattamente quello che sono d'accordo, non penso che sia sempre una brutta cosa fare un calcolo basato su loop in SQL @a_horse_with_no_name, a volte questo deve essere fatto comunque, preferirei che venga calcolato quando i dati vengono recuperati come indicato dalla metafora di Erwin. oppure devi ripetere questo a un costo quando i dati vengono recuperati.
tintinnio

-1 Perché è un argomento unilaterale, ignora i compromessi e crea un uomo di paglia per la parte avversaria invece di considerare e confutare il caso migliore della parte avversaria. "Andare avanti e indietro tra app e server è costoso" - assolutamente: ma non è l'unica cosa che è costosa e le varie spese devono essere valutate una contro l'altra. È possibile che query "SQL sofisticate" o stored procedure siano le migliori per il caso specifico; ma i dettagli del caso devono generalmente essere presi in considerazione quando si fa questo tipo di determinazione.
yfeldblum,

Bella analogia ma sfortunatamente si basa su ipotesi errate. La spedizione di minerale d'oro è molto comune. Il rapporto di strippaggio dell'oro è di circa 1: 1 (da oro a rifiuto), tuttavia è spesso più economico elaborarlo fuori sede, dove sono disponibili attrezzature e qualità di lavorazione migliori. A seconda delle dimensioni della spedizione, aumentare l'efficienza di elaborazione dello 0,1% può consentire un aumento relativo delle entrate (nonostante il doppio prezzo di spedizione), poiché l'oro è piuttosto costoso in questi giorni. Anche altri minerali, come ad esempio il ferro, vengono normalmente spediti (il rapporto di strippaggio del ferro è di circa il 60%!).
Chris Koston,

18

In questo caso probabilmente stai leggermente meglio facendo il calcolo in SQL poiché il motore di database avrà probabilmente una routine aritmetica decimale più efficiente di Java.

In genere, tuttavia, per i calcoli a livello di riga non c'è molta differenza.

Dove fa la differenza è:

  • Calcoli aggregati come SUM (), AVG (), MIN (), MAX () qui il motore di database sarà un ordine di grandezza più veloce di un'implementazione Java.
  • Ovunque il calcolo viene utilizzato per filtrare le righe. Il filtro nel DB è molto più efficiente della lettura di una riga e quindi della sua eliminazione.

12

Non c'è bianco / nero rispetto a quali parti della logica di accesso ai dati dovrebbero essere eseguite in SQL e quali parti dovrebbero essere eseguite nell'applicazione. Mi piace la frase di Mark Gravell , distinguendo tra

  • calcoli complessi
  • calcoli ad alta intensità di dati

Il potere e l'espressività di SQL sono fortemente sottovalutati. Dall'introduzione delle funzioni della finestra , molti calcoli non strettamente orientati al set possono essere eseguiti molto facilmente ed elegantemente nel database.

È necessario seguire sempre tre regole empiriche, indipendentemente dall'architettura generale dell'applicazione:

  • mantenere snella la quantità di dati trasferiti tra il database e l'applicazione (a favore del calcolo delle cose nel DB)
  • limitare la quantità di dati caricati dal disco dal database (per consentire al database di ottimizzare le istruzioni per evitare accessi non necessari ai dati)
  • non spingere il database ai limiti della CPU con calcoli complessi e simultanei (a favore di estrarre i dati nella memoria dell'applicazione ed eseguire calcoli lì)

Nella mia esperienza, con un DBA decente e una discreta conoscenza del tuo database decente, non ti imbatterai molto presto nei limiti della CPU dei tuoi DB.

Qualche ulteriore lettura in cui sono spiegate queste cose:


2

In generale, fare le cose in SQL se ci sono possibilità che anche altri moduli o componenti nello stesso o in altri progetti abbiano bisogno di ottenere quei risultati. un'operazione atomica eseguita sul lato server è anche migliore perché è sufficiente richiamare il proc memorizzato da qualsiasi strumento di gestione db per ottenere i valori finali senza ulteriore elaborazione.

In alcuni casi questo non si applica ma quando lo fa ha senso. anche in generale il db box ha i migliori hardware e prestazioni.


La riusabilità può essere presente a qualsiasi livello e non è un motivo (per quanto riguarda le prestazioni) per inserire più calcoli in SQL. "In generale il db box": questo è sbagliato e inoltre, come diceva marc gravell, il ridimensionamento non funziona allo stesso modo. La maggior parte dei database richiede un funzionamento decente dell'hardware ridotto e il modello di prestazioni ha poco a che fare con quello di un server delle applicazioni (ovvero spenderei 2/3 del mio budget per un server SQL su IO divino mentre non spenderei di più di poche centinaia per lo stack di archiviazione di un appserver).
Morg.

1

Se si sta scrivendo su ORM o si stanno scrivendo applicazioni casuali a basse prestazioni, utilizzare qualunque modello semplifichi l'applicazione. Se stai scrivendo un'applicazione ad alte prestazioni e stai pensando attentamente alla scala, vincerai spostando l'elaborazione sui dati. Consiglio vivamente di spostare il trattamento sui dati.

Pensiamo a questo in due passaggi: (1) Transazioni OLTP (piccolo numero di record). (2) OLAP (lunghe scansioni di molti record).

Nel caso OLTP, se si desidera essere veloci (transazioni da 10k a 100k al secondo), è necessario rimuovere la contesa tra latch, lock e dead lock dal database. Ciò significa che è necessario eliminare le lunghe bancarelle nelle transazioni: i round trip da client a DB per spostare l'elaborazione sul client sono una di queste. Non è possibile avere transazioni di lunga durata (per rendere atomica la lettura / aggiornamento) e un throughput molto elevato.

Ri: ridimensionamento orizzontale. I database moderni si ridimensionano orizzontalmente. Tali sistemi implementano già HA e tolleranza d'errore. Sfruttalo e cerca di semplificare lo spazio delle tue applicazioni.

Diamo un'occhiata a OLAP: in questo caso dovrebbe essere ovvio che il trascinamento di eventuali terrabyte di dati nell'applicazione sia un'idea orribile. Questi sistemi sono costruiti appositamente per funzionare in modo estremamente efficiente contro dati colonnari compressi e pre-organizzati. I moderni sistemi OLAP si ridimensionano anche in orizzontale e dispongono di sofisticati pianificatori di query che disperdono il lavoro in orizzontale (spostando internamente l'elaborazione verso i dati).


0

Se eseguire calcoli sul front-end o sul back-end è decisamente deciso se possiamo determinare il nostro obiettivo nell'implementazione aziendale. Al momento il codice java potrebbe funzionare meglio di un codice sql sia ben scritto che potrebbe essere viceversa. Ma ancora se confuso puoi provare a determinare prima -

  1. Se riesci a ottenere qualcosa di semplice tramite il database sql, allora vai meglio perché db si esibirà molto meglio e farà calcoli lì e poi con il risultato recupera. Tuttavia, se il calcolo effettivo richiede troppi calcoli da qui e là roba, allora puoi andare con il codice dell'applicazione. Perché? Perché il looping degli scenari nella maggior parte dei casi non è meglio gestito da sql, laddove le lingue front-end sono progettate meglio per queste cose.
  2. Nel caso in cui sia richiesto un calcolo simile da molti punti, ovviamente posizionare il codice di calcolo alla fine del database sarà meglio per mantenere le cose nello stesso posto.
  3. Se ci sono molti calcoli da fare per ottenere il risultato finale attraverso molte query diverse, allora vai anche per db end in quanto puoi inserire lo stesso codice in una procedura memorizzata per eseguire meglio del recupero dei risultati dal backend e poi calcolarli in primo piano fine.

Ci sono molti altri aspetti che puoi pensare prima di decidere dove posizionare il codice. Una percezione è totalmente sbagliata: tutto può essere fatto meglio in Java (codice app) e / o tutto è meglio fare dal db (codice sql).


0

Formare un punto di vista delle prestazioni: questa è un'operazione aritmetica molto semplice che quasi certamente può essere eseguita molto più velocemente rispetto al recupero effettivo dei dati dai dischi sottostanti al database. Inoltre, è probabile che il calcolo dei valori nella clausola where sia molto veloce in qualsiasi runtime. In sintesi, il collo di bottiglia dovrebbe essere IO del disco, non il calcolo dei valori.

Per quanto riguarda la leggibilità, penso che se usi un ORM, dovresti farlo nell'ambiente del tuo server di app, perché l'ORM ti consentirà di lavorare con i dati sottostanti molto facilmente, usando operazioni basate su set. Se hai intenzione di scrivere SQL grezzo comunque, non c'è nulla di sbagliato nel fare il calcolo lì, il tuo SQL sembrerebbe anche un po 'più bello e più facile da leggere se formattato correttamente.


0

Fondamentalmente, la "performance" non è definita.

Quello che conta di più per me è il tempo degli sviluppatori.

Scrivi la query SQL. Se è troppo lento o il DB diventa un collo di bottiglia, riconsiderare. A quel punto, sarai in grado di confrontare i due approcci e prendere la tua decisione sulla base di dati reali rilevanti per la tua configurazione (hardware e qualunque stack tu sia).


0

Non credo che le differenze di prestazioni possano essere ragionate senza esempi e benchmark specifici, ma ho un'altra opinione:

Quale puoi mantenere meglio? Ad esempio, potresti voler cambiare front-end da Java a Flash, HTML5, C ++ o qualcos'altro. Un gran numero di programmi ha subito un tale cambiamento, o addirittura esiste in più di una lingua, perché devono lavorare su più dispositivi.

Anche se hai un livello intermedio adeguato (dall'esempio fornito, sembra che non sia così), quel livello potrebbe cambiare e JBoss potrebbe diventare Ruby / Rails.

D'altra parte, è improbabile che sostituirai il back-end SQL con qualcosa che non è un DB relazionale con SQL e anche se lo fai, dovrai comunque riscrivere il front-end da zero, quindi il punto è controverso.

La mia idea è che se si eseguono calcoli nel DB, sarà molto più semplice scrivere un secondo front-end o strato intermedio in seguito, poiché non è necessario implementare nuovamente tutto. In pratica, tuttavia, penso che "dove posso farlo con il codice che la gente capirà" è il fattore più importante.


Se cambi da jboss a ruby, è molto probabile che cambi db (e dovrai comunque adottare questi calcoli) e non è così improbabile che tu possa cambiare in qualcosa di più diverso, come nosql.
Dainius,

0

Semplificare come rispondere a questo sarebbe guardare al bilanciamento del carico. Vuoi mettere il carico dove hai più capacità (se ha senso). Nella maggior parte dei sistemi è il server SQL che diventa rapidamente un collo di bottiglia, quindi la risposta probabilmente è che non si desidera che SQL faccia un'oncia di lavoro più del necessario.

Inoltre nella maggior parte delle architetture sono i server SQL a costituire il nucleo del sistema e i sistemi esterni che vengono aggiunti.

Ma la matematica sopra è così banale che a meno che tu non stia spingendo il tuo sistema al limite, il posto migliore per metterlo è dove vuoi metterlo. Se la matematica non fosse banale come il calcolo di sin / cos / tan per esempio un calcolo della distanza, lo sforzo potrebbe diventare non banale e richiedere un'attenta pianificazione e test.


0

Le altre risposte a questa domanda sono interessanti. Sorprendentemente, nessuno ha risposto alla tua domanda. Ti stai chiedendo:

  1. È meglio eseguire il cast su Cents nella query? Non credo che il cast in centesimi aggiunga qualcosa alla tua domanda.
  2. È meglio usare now () nella query? Preferirei passare le date nella query anziché calcolarle nella query.

Altre informazioni: per la prima domanda vuoi essere sicuro che l'aggregazione delle frazioni funzioni senza arrotondare gli errori. Penso che 19,2 numerico sia ragionevole per soldi e nel secondo caso gli interi sono OK. L'uso di un float per soldi è sbagliato per questo motivo.

Per la seconda domanda, mi piace avere il pieno controllo come programmatore di quale data è considerata "ora". Può essere difficile scrivere test unitari automatici quando si usano funzioni come now (). Inoltre, quando si dispone di uno script di transazione più lungo, può essere utile impostare una variabile uguale a now () e utilizzare la variabile in modo che tutta la logica utilizzi esattamente lo stesso valore.


0

Vorrei fare un esempio reale per rispondere a questa domanda

Avevo bisogno di calcolare una media mobile ponderata sui miei dati Ohlc, ho circa 134000 candele con un simbolo per ognuno per farlo

  1. Opzione 1 Fallo in Python / Node ecc ecc
  2. Opzione 2 Fallo in SQL stesso!

Qual è il migliore?

  • Se dovessi farlo in Python, in sostanza, dovrei recuperare tutti i record memorizzati nel peggiore dei casi, eseguire il calcolo e salvare tutto ciò che secondo me è un enorme spreco di IO
  • Variazioni medie mobili ponderate ogni volta che ricevi una nuova candela, il che significa che farei enormi quantità di IO a intervalli regolari, il che non è una buona opinione nel mio segno
  • In SQL, tutto ciò che devo fare è probabilmente scrivere un trigger che calcola e memorizza tutto, quindi è sufficiente recuperare i valori WMA finali per ogni coppia di tanto in tanto e questo è molto più efficiente

Requisiti

  • Se dovessi calcolare il WMA per ogni candela e memorizzarlo, lo farei su Python
  • Ma poiché ho solo bisogno dell'ultimo valore, SQL è molto più veloce di Python

Per darti un po 'di incoraggiamento, questa è la versione di Python per fare una media mobile ponderata

WMA fatto tramite codice

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 tramite 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()

Che ci crediate o no, la query viene eseguita più velocemente rispetto alla versione Pure Python di fare una MEDIA IN MOVIMENTO PESATA !!! Sono andato passo dopo passo nella stesura di quella query, quindi tieni duro e farai bene

Velocità

0,42141127300055814 secondi Python

0,23801879299935536 secondi SQL

Ho 134000 falsi record OHLC nel mio database divisi tra 1000 titoli, questo è un esempio di dove SQL può superare il tuo server di app


1
Tuttavia, se è necessario farlo milioni di volte il più rapidamente possibile, è molto più semplice generare app Python parallele rispetto alle repliche db. Fino a quando una certa scala che si appoggia di più su SQL è sicuramente più veloce / economica, ma alla fine c'è un punto critico quando è meglio fare questo calcolo nella tua applicazione.
Lenny,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.