web-dev-qa-db-ja.com

SQLとアプリケーションで計算を実行することの長所と短所は何ですか

shopkeeperテーブルには次のフィールドがあります。

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

たとえば、上記の表があります。昨日のレコードを取得し、金額をセントに印刷してレポートを生成したいと思います。

実行方法の1つは、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())

そして、レコードをループしてレポートを生成します

1つの方法では、すべての処理がJavaアプリケーションで行われ、単純なクエリが起動されます。それ以外の場合、すべての変換と計算はSQLクエリで行われます。

上記の使用例は単なる例であり、実際のシナリオでは、テーブルには、同様の種類の処理を必要とする多くの列を含めることができます。

パフォーマンスなどの面でどのアプローチが優れているか、そしてその理由を教えてください

137
hellojava

それは多くの要因に依存します-しかし最も重要なことは:

  • 計算の複雑さ(outをスケーリングするdbサーバーよりもoutをスケーリングするため、アプリサーバーで複雑な処理を行うことをお勧めします)
  • データ量(大量のデータにアクセス/集約する必要がある場合、dbサーバーで行うと帯域幅が節約され、集約がインデックス内で実行できる場合はディスクio)
  • 利便性(SQLは複雑な作業には最適な言語ではありません-特に手続き型の作業には適していませんが、セットベースの作業には非常に適しています。ただし、エラー処理はお粗末です)

いつものように、doを使用してデータをアプリサーバーに戻す場合、列と行を最小限に抑えることが有利になります。クエリが調整され、適切にインデックス付けされていることを確認すると、どちらのシナリオにも役立ちます。

メモを再:

そして、レコードをループします

Looping through recordsは、ほとんどの場合、sqlで行うのが間違っています-セットベースの操作を記述することをお勧めします。

原則として、データベースのジョブを最小限に抑えることをお勧めします。「このデータを保存し、このデータをフェッチします」-ただし、サーバーでのエレガントなクエリが多くの帯域幅。

また、考慮してください:これが計算上高価な場合、どこかにキャッシュできますか?

正確な "これは良い";両方の方法でコーディングし、比較します(どちらかの最初のドラフトは100%調整されていない可能性が高いことに注意してください)。しかし、通常の使用法を考慮してください。実際に一度に5回(別々に)呼び出される場合は、それをシミュレートします。「これらの1つと1つ」だけを比較しないでください。

193
Marc Gravell

比phorを使ってみましょう:パリで金のネックレスを購入したい場合、金細工師はケープタウンまたはパリに座ることができます、それは問題ですスキルと味。しかし、そのために南アフリカからフランスに大量の金鉱石を決して出荷しないでしょう。鉱石は採掘現場(または少なくとも一般的な地域)で処理され、金のみが出荷されます。同じことがアプリとデータベースにも当てはまります。

PostgreSQLに関する限り、サーバー上でほとんど何でも非常に効率的に実行できます。 RDBMSは、複雑なクエリに優れています。手続き上のニーズについては、さまざまな サーバー側スクリプト言語 :tcl、python、Perlなどから選択できます。ただし、ほとんどは PL/pgSQL を使用します。

最悪の場合のシナリオは、より大きなセットの各行ごとにサーバーに繰り返しアクセスすることです。 (それは、一度に1トンの鉱石を出荷するようなものです。)

2番目の行、前のクエリに依存するクエリのカスケードを送信する場合、すべてのクエリまたはプロシージャですべてを実行できますサーバー。 (これは、金と各宝石を別々の船で順番に出荷するようなものです。)

アプリとサーバーを行き来するのは高価です。サーバーおよびクライアントの場合。それを削減しようとすると、勝ちます-エルゴ:必要に応じてサーバー側の手順や洗練されたSQLを使用します。

ほぼすべての複雑なクエリをPostgres関数にパックするプロジェクトを終了しました。アプリはパラメーターを引き渡し、必要なデータセットを取得します。高速、クリーン、シンプル(アプリ開発者向け)、I/Oは最小限に抑えられています...カーボンフットプリントの少ない光沢のあるネックレス。

78

この場合、データベースエンジンはJavaよりも効率的な10進算術ルーチンを持っている可能性が高いため、SQLで計算を行う方がおそらくわずかに優れています。

一般に、行レベルの計算ではそれほど違いはありません。

違いを生むのは次のとおりです。

  • SUM()、AVG()、MIN()、MAX()などの集計計算では、データベースエンジンはJava実装よりも桁違いに高速になります。
  • 行のフィルタリングに計算が使用される場所。 DBでのフィルタリングは、行を読み取ってから破棄するよりもはるかに効率的です。
17
James Anderson

データアクセスロジックのどの部分をSQLで実行し、どの部分をアプリケーションで実行するかに関して、黒/白はありません。 Mark Gravell's 文言が好きで、区別します

  • 複雑な計算
  • データ集約型の計算

SQLのパワーと表現力は、過小評価されています。 window functions が導入されたため、多くの非厳密セット指向の計算をデータベースで非常に簡単かつエレガントに実行できます。

アプリケーションアーキテクチャ全体に関係なく、常に3つの経験則に従う必要があります。

  • データベースとアプリケーションの間で転送されるデータ量をスリムに保ちます(DB内のデータを計算するため)
  • データベースによってディスクからロードされるデータの量をスリムに保ちます(データベースがステートメントを最適化して不要なデータアクセスを回避できるようにします)
  • 複雑な同時計算を使用してデータベースをCPU制限にプッシュしないでください(データをアプリケーションメモリに取り込み、そこで計算を実行することを支持します)

私の経験では、まともなDBAとまともなデータベースに関するある程度の知識があれば、すぐにDBのCPU制限に達することはありません。

これらの事柄が説明されているいくつかのさらなる読書:

12
Lukas Eder

一般に、同じまたは他のプロジェクトの他のモジュールまたはコンポーネントもそれらの結果を取得する必要がある場合は、SQLで処理を行います。サーバー側で実行されるアトミック操作も優れています。db管理ツールからストアドプロシージャを呼び出すだけで、さらに処理することなく最終的な値を取得できます。

場合によってはこれは適用されませんが、実行されると意味があります。また、一般的に、dbボックスは最高のハードウェアとパフォーマンスを備えています。

2
Davide Piras

ORMの上に記述したり、カジュアルな低パフォーマンスアプリケーションを記述したりする場合は、アプリケーションを単純化するパターンを使用してください。高性能のアプリケーションを作成し、規模について慎重に考えている場合、処理をデータに移行することで勝ちます。処理をデータに移行することを強く推奨します。

これについて、2つのステップで考えてみましょう。(1)OLTP(レコードの少数)トランザクション。 (2)OLAP(多くのレコードの長時間スキャン)。

OLTPの場合、高速(1秒あたり1万-1万トランザクション)にしたい場合は、データベースからラッチ、ロック、およびデッドロックの競合を削除する必要があります。これは、トランザクションでの長いストールを排除する必要があることを意味します。クライアントからDBへのラウンドトリップは、クライアントに処理を移動することです。 (読み取り/更新をアトミックにするための)長期間存続するトランザクションを使用することはできず、スループットは非常に高くなります。

再:水平スケーリング。最新のデータベースは水平方向に拡張します。これらのシステムは、HAとフォールトトレランスを既に実装しています。それを活用して、アプリケーションのスペースを簡素化してください。

OLAPを見てみましょう-この場合、おそらくテラバイトのデータをアプリケーションにドラッグバックするのは恐ろしいアイデアであることは明らかです。これらのシステムは、圧縮された事前に編成された列データに対して非常に効率的に動作するように特別に構築されています。最新のOLAPシステムも水平方向にスケーリングし、作業を水平方向に分散させる高度なクエリプランナーを備えています(内部的に処理をデータに移動します)。

1
Ryan

これに答える方法を簡素化するには、負荷分散を調べることです。負荷を最も容量の大きい場所に配置したい(それが理にかなっている場合)。ほとんどのシステムでは、すぐにボトルネックになるのはSQLサーバーなので、おそらく答えは、SQLが必要以上に1オンスの作業を行うことは望ましくないということです。

また、ほとんどのアーキテクチャでは、追加されるシステムと外部システムのコアを構成するのはSQLサーバーです。

しかし、上記の計算は非常に簡単なので、システムを限界までプッシュしない限り、配置するのに最適な場所は配置したい場所です。距離計算などのためにsin/cos/tanを計算するなど、数学が自明でない場合、努力は自明ではなくなり、慎重な計画とテストが必要になる可能性があります。

0
Donovanr

この質問に対処するために実際の例を挙げましょう

私はOHLCデータの加重移動平均を計算する必要がありました、それを行うためのシンボルを持つ約134000のキャンドルがあります

  1. オプション1 Python/Nodeなどで実行する
  2. オプション2 SQL自体で実行してください!

どちらが良いですか?

  • Pythonでこれをしなければならなかった場合、本質的に、最悪の場合はすべての保存されたレコードをフェッチし、計算を実行し、すべてを保存する必要がありますが、これはIOの巨大な無駄です
  • 加重移動平均は、新しいキャンドルを取得するたびに変化します。つまり、定期的に大量の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 Through 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()

信じられないかもしれませんが、クエリは純粋なPythonバージョンよりも高速に実行されますWEIGHTED MOVING AVERAGE !!!私はそのクエリを段階的に作成しましたそこにハングアップして、あなたはうまくやるよ

速度

0.42141127300055814秒Python

0.23801879299935536秒SQL

私のデータベースには134000の偽OHLCレコードがあり、1000の株式に分割されています。これは、SQLがアプリサーバーよりも優れている例です

0
PirateApp

パフォーマンスの観点を形成する:これは、データベースの基になるディスクから実際にデータをフェッチするよりもはるかに高速に実行できる非常に単純な算術演算です。また、where句の値の計算は、実行時に非常に高速になる可能性があります。要約すると、ボトルネックは値の計算ではなく、ディスクIOである必要があります。

読みやすさから、ORMを使用する場合は、アプリケーションサーバー環境で実行する必要があると思います。ORMを使用すると、セットベースの操作を使用して、基になるデータを非常に簡単に操作できるからです。とにかく生のSQLを作成する場合、そこで計算を行うのに何も問題はありません。適切にフォーマットされていれば、SQLは少し見やすく、読みやすくなります。

0
Johannes Gehrs

この質問に対する他の回答は興味深いものです。 驚いたことに、誰もあなたの質問に答えていません。あなたは疑問に思っています:

  1. クエリでセントにキャストする方が良いですか?セントへのキャストがクエリに何かを追加するとは思わない。
  2. クエリでnow()を使用する方が良いですか?クエリで日付を計算するのではなく、クエリに日付を渡したいと思います。

詳細:質問1では、端数の集計が丸め誤差なしで機能することを確認する必要があります。私は、19,2という数値はお金に見合ったものだと思います。このため、お金にフロートを使用するのは間違っています。

質問2では、プログラマーとして「現在」と見なされる日付を完全に制御したいと思います。 now()のような関数を使用する場合、自動ユニットテストを記述するのは難しい場合があります。また、トランザクションスクリプトが長い場合は、変数をnow()に設定し、その変数を使用して、すべてのロジックでまったく同じ値を使用することをお勧めします。

0
Chris Schoon

重要なのは、「パフォーマンス」が定義されていないことです。

私にとって最も重要なのは開発者の時間です。

SQLクエリを記述します。遅すぎるか、DBがボトルネックになっている場合は、再検討してください。その時までに、2つのアプローチのベンチマークを行い、セットアップに関連する実際のデータ(ハードウェアと使用しているスタック)に基づいて決定を下せるようになります。

0
user2757750

パフォーマンスの違いは、特定の例やベンチマークなしで推論できるとは思いませんが、別の見解があります。

どちらを維持できますか?たとえば、フロントエンドをJavaからFlash、HTML5、C++などに切り替えることができます。非常に多くのプログラムがこのような変更を経ており、複数のデバイスで動作する必要があるため、そもそも複数の言語で存在しています。

適切な中間層がある場合でも(与えられた例からそうではないようです)、その層が変更され、JBossがRuby/Railsになる可能性があります。

一方、SQLバックエンドをリレーショナルデータベースではないものでSQLに置き換えることはまずありませんし、たとえそれを行ってもフロントエンドをゼロから書き直さなければならないので、要点は重要ではありません。

私の考えは、DBで計算を行うと、すべてを再実装する必要がないため、後から2番目のフロントエンドまたは中間層を作成する方がはるかに簡単になるということです。ただし、実際には、「人々が理解できるコードでどこでこれを行うことができるか」が最も重要な要素だと思います。

0
Kajetan Abt

フロントエンドで計算を実行するかバックエンドで計算を実行するかは、ビジネスの実装における目標を決定できる場合、非常に決定されます。時にJavaコードは、SQLコードよりも適切に記述されているか、その逆の場合があります。しかし、まだ混乱している場合は、最初に判断しようとすることができます-

  1. データベースsqlを使用して簡単に何かを達成できる場合は、dbの方がはるかに優れたパフォーマンスを発揮し、そこで結果をフェッチしてから計算を行うため、それを使用する方が適切です。ただし、実際の計算があちこちで非常に多くの計算を必要とする場合は、アプリケーションコードを使用できます。どうして?ほとんどの場合、ループのようなシナリオはSQLによって最適に処理されないため、フロントエンド言語はこれらの目的に適した設計になっています。
  2. 多くの場所で同様の計算が必要な場合は、明らかに計算コードをdbの最後に配置する方が、同じ場所に物事を保持する方が良いでしょう。
  3. 多くの異なるクエリを介して最終結果を達成するために多くの計算が必要な場合は、db endを使用して同じコードをストアドプロシージャに配置し、バックエンドから結果を取得してフロントで計算するよりもパフォーマンスを向上させることができます終わり。

コードを配置する場所を決定する前に、考えられる他の多くの側面があります。 1つの認識が完全に間違っています-すべてがJava(アプリコード)で最適に実行でき、かつ/またはすべてがdb(SQLコード)で最適です。

0
Neo