La table shopkeeper
a les champs suivants:
id (bigint),amount (numeric(19,2)),createddate (timestamp)
Disons que j'ai le tableau ci-dessus. Je veux obtenir les enregistrements d’hier et Générer un rapport en imprimant le montant en cents.
Une façon de faire est d'effectuer des calculs dans mon application Java et d'exécuter une requête simple
Date previousDate ;// $1 calculate in application
Date todayDate;// $2 calculate in application
select amount where createddate between $1 and $2
puis parcourez les enregistrements et convertissez le montant en centimes dans mon application Java et générez le rapport
Une autre façon est comme effectuer des calculs dans une requête SQL elle-même:
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())
puis parcourez les enregistrements et générez le rapport
D'une manière, tous mes traitements sont effectués dans une application Java et une requête simple est lancée . Dans un autre cas, toutes les conversions et tous les calculs sont effectués dans une requête SQL.
Le cas d'utilisation ci-dessus n'est qu'un exemple. Dans un scénario réel, une table peut comporter de nombreuses colonnes nécessitant un traitement similaire.
Pouvez-vous s'il vous plaît me dire quelle approche est la meilleure en termes de performance et d'autres aspects et pourquoi?
Cela dépend de nombreux facteurs, mais le plus crucial:
Comme toujours, si vous faites ramener les données sur le serveur d'applications, la réduction du nombre de colonnes et de lignes vous avantage. S'assurer que la requête est réglée et indexée de manière appropriée aidera l'un ou l'autre scénario.
Re votre note:
puis parcourez les enregistrements
Boucler dans les enregistrements est presque toujours la mauvaise chose à faire dans SQL - l'écriture d'une opération basée sur un ensemble est préférable.
En règle générale, je préfère garder le travail de la base de données au minimum "stocker ces données, récupérer ces données" - cependant, il existe toujours des cas de scénarios dans lesquels une requête élégante sur le serveur peut économiser beaucoup de bande passante. .
Pensez également: si cela coûte cher en calcul, peut-il être mis en cache quelque part?
Si vous voulez un précis "ce qui est mieux"; codez-le dans les deux sens et comparez-le (en notant qu'un premier brouillon n'est probablement pas réglé à 100%). Mais tenez compte de l’utilisation typique: si, en réalité, il est appelé 5 fois (séparément) à la fois, simulez cela: ne comparez pas un seul "1 de ces éléments à 1 de ceux-ci".
Permettez-moi d’utiliser une métaphore: si vous voulez acheter un collier en or à Paris, l’orfèvre pourrait siéger au Cap ou à Paris, c’est une question de compétence et goût. Mais vous voudriez jamais expédier des tonnes de minerai d'or d'Afrique du Sud en France pour cela. Le minerai est traité sur le site minier (ou au moins dans la région), seul l'or est expédié. La même chose devrait être vraie pour les applications et les bases de données.
En ce qui concerne PostgreSQL , vous pouvez faire presque tout sur le serveur, de manière très efficace. Le SGBDR excelle dans les requêtes complexes. Pour les besoins de procédure, vous pouvez choisir parmi une variété de langages de script côté serveur : tcl, python, Perl et bien d’autres. La plupart du temps j'utilise PL/pgSQL , cependant.
Dans le pire des cas , le scénario serait de consulter régulièrement le serveur pour chaque ligne d'un ensemble plus volumineux. (Ce serait comme expédier une tonne de minerai par heure.)
Deuxième ligne , si vous envoyez une cascade de requêtes, chacune en fonction de la précédente, alors que tout cela peut être fait en une seule requête ou procédure le le serveur. (C'est comme si vous expédiez l'or, et chacun des bijoux avec un navire séparé, de manière séquentielle.)
Aller et venir entre l'application et le serveur coûte cher. Pour le serveur and client. Essayez de réduire cela, et vous gagnerez - ergo: utilisez les procédures côté serveur et/ou le SQL sophistiqué si nécessaire.
Nous venons de terminer un projet dans lequel nous intégrons presque toutes les requêtes complexes dans les fonctions Postgres. L'application transmet les paramètres et récupère les jeux de données dont elle a besoin. Rapide, propre, simple (pour le développeur de l'application), I/O réduite au minimum ... un collier brillant avec une faible empreinte carbone.
Dans ce cas, probablement légèrement mieux vaut faire le calcul en SQL car le moteur de base de données aura probablement des routines arithmétiques décimales plus efficaces que Java.
En règle générale, il n'y a pas beaucoup de différence pour les calculs au niveau des lignes.
Voici ce qui fait la différence:
Il n'y a pas de noir/blanc quant aux parties de la logique d'accès aux données qui doivent être exécutées en SQL et aux parties qui doivent être exécutées dans votre application. J'aime Mark Gravell formulation, distinguant entre
La puissance et l'expressivité de SQL sont fortement sous-estimées. Depuis l’introduction de fonctions de fenêtre , de nombreux calculs non strictement orientés sur les ensembles peuvent être effectués très facilement et avec élégance dans la base de données.
Trois règles empiriques doivent toujours être suivies, quelle que soit l'architecture globale de l'application:
D'après mon expérience, avec un administrateur de base de données correct et une connaissance approfondie de votre base de données, vous ne rencontrerez pas très rapidement vos limites de processeurs de bases de données.
Quelques lectures supplémentaires où ces choses sont expliquées:
En règle générale, effectuez des opérations SQL s'il est probable que d'autres modules ou composants de projets identiques ou similaires devront également obtenir ces résultats. une opération atomique effectuée côté serveur est également préférable car il vous suffit d'appeler le processus stocké à partir de n'importe quel outil de gestion de base de données pour obtenir les valeurs finales sans traitement supplémentaire.
Dans certains cas, cela ne s'applique pas, mais quand c'est le cas, cela a du sens. aussi en général la boîte de dialogue a le meilleur matériel et les meilleures performances.
Si vous écrivez sur ORM ou écrivez des applications occasionnelles peu performantes, utilisez le modèle qui simplifie l'application. Si vous écrivez une application hautes performances et réfléchissez bien à l’échelle, vous gagnerez en transférant le traitement vers les données. Je recommande fortement de déplacer le traitement vers les données.
Pensons à cela en deux étapes: (1) Transactions OLTP (petit nombre d’enregistrements). (2) OLAP (analyses longues de nombreux enregistrements).
Dans le cas OLTP, si vous voulez être rapide (10 000 - 100 000 transactions par seconde), vous devez supprimer les conflits de verrouillage, verrouillé et verrou mort de la base de données. Cela signifie que vous devez éliminer les longs blocages dans les transactions: les allers-retours entre client et base de données pour transférer le traitement sur le client constituent un tel blocage. Vous ne pouvez pas avoir de transactions à long terme (rendre lecture/mise à jour atomique) et avoir un débit très élevé.
Re: mise à l'échelle horizontale. Les bases de données modernes évoluent horizontalement. Ces systèmes implémentent déjà la haute disponibilité et la tolérance aux pannes. Tirez parti de cela et essayez de simplifier votre espace d'application.
Regardons OLAP - dans ce cas, il devrait être évident que ramener éventuellement des terrabytes de données dans l'application est une idée horrible. Ces systèmes sont spécialement conçus pour fonctionner de manière extrêmement efficace contre les données en colonnes compressées et pré-organisées. Les systèmes modernes OLAP évoluent également horizontalement et disposent de planificateurs de requêtes sophistiqués qui dispersent le travail horizontalement (déplacement interne du traitement vers les données).
Pour simplifier la façon de répondre à cette question, il faudrait examiner l'équilibrage de la charge. Vous voulez placer la charge là où vous avez le plus de capacité (si cela a du sens). Dans la plupart des systèmes, c'est le serveur SQL qui devient rapidement un goulot d'étranglement. La réponse probable est donc que vous ne voulez pas que SQL fasse une once de travail plus que nécessaire.
De plus, dans la plupart des architectures, ce sont le ou les serveurs SQL qui constituent le cœur du système et les systèmes extérieurs qui sont ajoutés.
Mais le calcul ci-dessus est si trivial que, sauf si vous poussez votre système à l'extrême, le meilleur endroit pour le mettre est l'endroit où vous voulez le mettre. Si les calculs n'étaient pas triviaux, tels que le calcul de sin/cos/tan pour un calcul de distance, l'effort pourrait devenir non trivial et nécessiter une planification et des tests minutieux.
Permettez-moi de prendre un exemple concret pour répondre à cette question.
Je devais calculer une moyenne mobile pondérée sur mes données ohlc. J'ai environ 134 000 bougies avec un symbole pour chacune d'elles.
Quel est le meilleur?
Exigences
Pour vous encourager, voici la version Python permettant de calculer une moyenne mobile pondérée.
WMA fait par code
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 via 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()
Croyez-le ou non, la requête est plus rapide que la version Pure Python de la moyenne pondérée mobile !!! Je suis allé pas à pas dans la rédaction de cette requête, alors accrochez-vous et vous vous en sortirez très bien
La vitesse
0.42141127300055814 secondes Python
0.23801879299935536 secondes SQL
J'ai 134 000 faux enregistrements OHLC dans ma base de données, répartis sur 1 000 actions. Voici un exemple d'utilisation optimale de SQL par votre serveur d'applications.
Formez un point de vue des performances: il s'agit d'une opération arithmétique très simple qui peut presque certainement être effectuée beaucoup plus rapidement que d'extraire réellement les données des disques sous-jacents à la base de données. En outre, le calcul des valeurs de la clause where est susceptible d’être très rapide, peu importe l’exécution. En résumé, le goulot d'étranglement devrait être le disque IO, pas le calcul des valeurs.
En ce qui concerne la lisibilité, je pense que si vous utilisez un ORM, vous devriez le faire dans votre environnement de serveur d'applications, car l'ORM vous permettra de travailler très facilement avec les données sous-jacentes, à l'aide d'opérations basées sur les ensembles. De toute façon, si vous envisagez d'écrire du SQL brut, il n'y a rien de mal à faire le calcul ici. Votre SQL paraîtrait aussi un peu plus joli et plus facile à lire s'il est correctement formaté.
Les autres réponses à cette question sont intéressantes. Étonnamment, personne n'a répondu à votre question. Vous vous demandez:
Plus d'infos: Pour la question un, vous voulez être sûr d’agréger les fractions fonctionne sans erreurs d'arrondi. Je pense que numeric 19,2 est raisonnable pour de l'argent et dans le second cas, les nombres entiers sont OK. Utiliser un flottant pour de l'argent est faux pour cette raison.
Pour la deuxième question, j'aime bien avoir le plein contrôle en tant que programmeur de quoi la date est considérée comme "maintenant". Il peut être difficile d’écrire une unité automatique teste lors de l'utilisation de fonctions comme now (). Aussi, quand vous avez un plus long script de transaction, il peut être utile de définir une variable égale à now () et d’utiliser la variable so que toute la logique utilise exactement la même valeur.
Surtout, la "performance" n'est pas définie.
Celui qui compte le plus pour moi est le temps consacré aux développeurs.
Écrivez la requête SQL. S'il est trop lent ou si la base de données devient un goulot d'étranglement, réexaminez-la. À ce moment-là, vous pourrez analyser les deux approches et prendre votre décision en fonction de données réelles pertinentes pour votre configuration (matériel et pile sur laquelle vous vous trouvez).
Je ne crois pas que les différences de performances puissent être raisonnées sans exemples et points de repère spécifiques, mais j’en ai une autre:
Que pouvez-vous maintenir mieux? Par exemple, vous voudrez peut-être passer de Java à Flash, HTML5, C++ ou autre. Un grand nombre de programmes ont subi un tel changement, voire existent dans plusieurs langues, car ils doivent fonctionner sur plusieurs appareils.
Même si vous avez un calque intermédiaire approprié (d'après l'exemple donné, il semble que ce ne soit pas le cas), ce calque peut changer et JBoss peut devenir Ruby/Rails.
D'autre part, il est peu probable que vous remplaciez le backend SQL par quelque chose qui ne soit pas une base de données relationnelle avec SQL et même si vous le faites, vous devrez quand même réécrire le front-end à partir de zéro.
Mon idée est que si vous effectuez des calculs dans la base de données, il sera beaucoup plus facile d'écrire une deuxième couche frontale ou intermédiaire, car vous n'avez pas à tout ré-implémenter. En pratique cependant, je pense que "où puis-je faire cela avec du code que les gens comprendront" est le facteur le plus important.
Que ce soit pour effectuer des calculs au début ou à l’arrière-plan est tout à fait décisif si nous pouvons déterminer notre objectif dans la mise en œuvre de l’entreprise. À l’époque, le code Java pourrait être plus performant qu’un code SQL bien écrit ou inversement. Mais quand même confus, vous pouvez essayer de déterminer d'abord -
Vous pouvez réfléchir à de nombreux autres aspects avant de décider où placer le code. Une perception est totalement fausse - tout peut être mieux fait en Java (code d'application) et/ou tout est préférable de le faire par la base de données (code SQL).