在sql和应用程序中执行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在除前端之外的任何层上都不太可能变慢。无论哪种情况,您的计算量都是最小的,并且与周围的操作相比显得相形见,,而与性能无关。
Morg。

Answers:


206

这取决于很多因素-但最关键的是:

  • 计算的复杂性(喜欢的应用程序的服务器上做复杂的捣鼓,因为这秤出来,而不是一个数据库服务器,它扩展
  • 数据量(如果您需要访问/聚合大量数据,那么在数据库服务器上进行操作将节省带宽,如果可以在索引内完成聚合,则可以节省磁盘io)
  • 便利性(sql并不是用于复杂工作的最佳语言-尤其不适用于程序工作,但对于基于集合的工作则非常好;但是糟糕的错误处理)

与往常一样,如果您确实将数据带回应用服务器,则最小化列和行将对您有利。确保查询已调优并正确索引,将有助于解决上述两种情况。

请注意:

然后遍历记录

循环通过记录几乎总是错误的东西在SQL做的事-写作基于集合的操作是首选。

通常,我希望将数据库的工作保持在最低限度,以“存储此数据,获取此数据”-但是,总是有一些场景示例,其中在服务器上进行优雅的查询可以节省大量带宽。

还要考虑:如果这在计算上很昂贵,可以将其缓存在某个地方吗?

如果您想要准确的 “更好”;两种方式进行编码并进行比较(请注意,两种格式的初稿都可能不会100%调整)。但是要考虑典型用法:如果实际上一次被调用5次(分别),则模拟一下:不要只比较一个“ 1个vs其中1个”。


循环牵涉到或多或少的“一次行”处理。这意味着2 *网络延迟和四个上下文切换往返。是的:那很贵。“本机” DBMS操作会尽一切努力来最小化磁盘I / O(系统调用),但每个系统调用设法获取多行。一次行至少需要四个系统调用。
wildplasser 2011年

@wildplasser不是必需的;服务器可能正在流式传输您在到达时消费的行-“阅读器”的隐喻并不罕见。
马克·格雷夫

1
@马克·卡维尔:嗯,这取决于。如果应用程序的占用空间仅是一个逻辑记录,则或多或少可以。但是,我知道的大多数“框架”在启动时都会吸收所有记录,然后逐一解雇。锁定是另一个陷阱。
wildplasser 2011年

我认为一个好的经验法则是:不要从SQL Server中带走最终不需要的数据行。例如,如果必须执行聚合操作,则它们可能属于SQL。表或子查询之间的联接?SQL。这也是我们使用徽章的方法,到目前为止,我们正在应对规模问题:-)
Sklivvz 2013年

1
@zinking这将是基于集合的操作。在这种情况下,您无需编写循环代码-这是实现细节。“循环”是指显式循环,例如游标
Marc Gravell

86

让我用一个比喻:如果您想在巴黎购买一条金项链,那么金匠可以坐在开普敦或巴黎,那是技巧和品味的问题。但是,您绝不会因此而将大量吨的金矿石从南非运到法国。矿石在采矿现场(或至少在一般区域)进行处理,仅运送黄金。应用程序和数据库也应如此。

PostgreSQL而言,您几乎可以在服务器上完成几乎所有事情。RDBMS擅长处理复杂的查询。对于程序需求,您可以从多种服务器端脚本语言中进行选择:tcl,python,perl等。不过,大多数情况下,我使用PL / pgSQL

最坏的情况是要针对较大集合的每一行重复访问服务器。(这就像一次运送一吨矿石。)

第二行,如果您发送一系列查询,每个查询都取决于之前的查询,而所有查询都可以在服务器上的一个查询或过程中完成。(这就像按顺序运送黄金和每件珠宝。)

在应用程序和服务器之间来回切换非常昂贵。对于服务器客户端。尝试减少这种情况,您会赢-ergo:在必要时使用服务器端过程和/或复杂的SQL。

我们刚刚完成了一个项目,其中将几乎所有复杂的查询打包到Postgres函数中。该应用程序移交参数并获取所需的数据集。快速,干净,简单(适用于应用程序开发人员),I / O降至最低……一条低碳足迹的闪亮项链。


12
对于使用这种类比与其他开发人员进行有意义的设计决策,我会保持谨慎。类比更多地是一种修辞手段,而不是逻辑上的手段。除其他因素外,将数据运送到应用服务器要比将黄金矿石运送给金匠便宜得多。
道格

3
您将根据更便宜的价格发送矿石或黄金,如果您没有将矿石转换为黄金的技术,或者将其转换为昂贵的价格(因为矿工想杀死这些其他工人),则您会将其运送到另一个地点,也许是在在金匠和矿工之间,尤其是如果您有一个以上的金匠。
Dainius

1
完全同意我的观点,我认为在SQL @a_horse_with_no_name中进行基于循环的计算并不总是一件坏事,无论如何有时还是必须这样做,我宁愿在按Erwin的隐喻指示提取数据时进行计算。否则您必须在取回数据时以一定的代价重复执行此操作。
zinking 2013年

-1因为是单方面的论点,所以忽略了权衡取舍,而是为反对方建立了一个稻草人,而不是考虑和反驳反对方的最佳情况。“在应用程序和服务器之间来回转换非常昂贵”-绝对:但这不是唯一昂贵的东西,而且各种费用必须相互权衡。事实证明,“复杂的SQL”查询或存储过程最适合特定情况。但是,在做出此类决定时,通常必须考虑到案件的细节。
yfeldblum 2013年

很酷的类比,但不幸的是,它是基于错误的假设。运输金矿石很常见。金的剥离率约为1:1(金与废料),但是在异地进行处理通常会更便宜,因为这里可以提供更好的设备和质量。根据装运量的不同,将处理效率提高0.1%可以使收入相对增加(尽管装运价格提高了一倍),因为这些天黄金非常昂贵。其他矿石,例如铁,通常也要运输(铁的剥离率约为60%!)。
克里斯·科斯顿

18

在这种情况下,使用SQL进行计算可能会更好一些,因为数据库引擎可能具有比Java更有效的十进制算术例程。

通常,尽管对于行级别的计算并没有太大的区别。

它的不同之处在于:

  • 像SUM(),AVG(),MIN(),MAX()这样的聚合计算在这里,数据库引擎将比Java实现快一个数量级。
  • 计算可用于过滤行的任何地方。与读取行然后丢弃行相比,在DB处进行过滤要有效得多。

12

关于应在SQL中执行数据访问逻辑的哪些部分以及应在应用程序中执行哪些部分,没有黑/白。我喜欢Mark Gravell的措辞,区分

  • 复杂的计算
  • 数据密集型计算

SQL的功能和表达能力被严重低估了。由于引入了窗口函数,因此可以非常轻松且优雅地在数据库中执行许多非严格面向集合的计算。

无论总体应用程序体系结构如何,都应始终遵循三个经验法则:

  • 保持数据库和应用程序之间的数据传输量较小(有利于计算数据库中的数据)
  • 保持数据库苗条地从磁盘加载的数据量(有利于让数据库优化语句以避免不必要的数据访问)
  • 不要通过复杂的并发计算将​​数据库推到其CPU极限(有利于将数据拉入应用程序内存并在那里进行计算)

以我的经验,有了一个体面的DBA和一些体面的数据库知识,您很快就不会遇到数据库的CPU限制。

一些进一步的阅读,解释了这些东西:


2

通常,如果有可能同一项目或其他项目中的其他模块或组件也需要获得这些结果,则可以在SQL中执行操作。在服务器端执行原子操作也更好,因为您只需要从任何数据库管理工具调用存储的proc即可获得最终值,而无需进一步处理。

在某些情况下,这并不适用,但确实适用。通常,db盒具有最佳的硬件和性能。


可重用性可以存在于任何层,而不是在SQL中进行更多计算的理由(从性能角度考虑)。“通常是db box”:这是错误的,而且,正如marc grall所说,扩展不能以相同的方式进行。大多数数据库只需要很少的硬件就可以正常运行,并且性能模式与应用程序服务器的性能几乎没有关系(即,我将2/3的预算花在用于IO的SQL服务器上,而我不会花更多的钱)而不是应用服务器存储堆栈的数百个)。
Morg。

1

如果您是在ORM之上编写或编写临时的低性能应用程序,则使用任何可简化应用程序的模式。如果您正在编写一个高性能的应用程序并仔细考虑规模问题,那么将处理转移到数据上将会获得成功。我强烈建议将处理移至数据。

让我们分两步考虑一下:(1)OLTP(少量记录)事务。(2)OLAP(对许多记录的长时间扫描)。

在OLTP情况下,如果要提高速度(每秒10k-100k事务),则必须从数据库中删除闩锁,锁和死锁争用。这意味着您需要消除事务中的长时间停顿:从客户端到数据库的往返行程以将处理转移到客户端就是一个如此长的停顿。您不能有长寿的事务(使读取/更新成为原子),并且不能具有很高的吞吐量。

回复:水平缩放。现代数据库是水平扩展的。这些系统已经实现了HA和容错功能。利用它并尝试简化您的应用程序空间。

让我们看一下OLAP-在这种情况下,很明显,将可能是terrabytes的数据拖回应用程序是一个可怕的想法。这些系统是专门为针对压缩的,预先组织的柱状数据而极为有效地构建的。现代的OLAP系统还可以水平扩展,并具有复杂的查询计划器,可以水平分散工作(内部将处理移至数据)。


0

是否确定业务实施目标是非常决定要在前端还是在后端进行计算的。有时,编写良好的Java代码可能比编写sql代码的性能更好,或者反之亦然。但是,如果仍然感到困惑,您可以尝试先确定-

  1. 如果您可以通过数据库sql实现简单明了的操作,那么最好这样做,因为db的性能会更好,并在那里进行计算,然后进行结果提取。但是,如果实际计算需要从头到尾进行过多的计算,则可以使用应用程序代码。为什么?因为在大多数情况下,场景之类的循环不是由sql最佳处理的,所以asease前端语言更适合这些情况。
  2. 如果在许多地方都需要进行类似的计算,那么显然将计算代码放在db端会更好地将事情保留在同一位置。
  3. 如果要通过许多不同的查询来完成许多计算以获得最终结果,那么也要进行数据库结束,因为您可以将相同的代码放入存储过程中,以比从后端检索结果然后在前端进行计算更好。结束。

在决定将代码放置在何处之前,您还需要考虑许多其他方面。一种看法是完全错误的-一切都可以用Java(应用程序代码)最好地完成,和/或一切最好由db(sql代码)完成。


0

从性能的角度来看:这是一个非常简单的算术运算,几乎可以肯定比实际上从数据库下的磁盘获取数据要快得多。同样,在任何运行时中,计算where子句中的值可能非常快。总之,瓶颈应该是磁盘IO,而不是值的计算。

就可读性而言,我认为如果您使用ORM,则应在应用服务器环境中进行操作,因为ORM将使您能够使用基于集合的操作非常轻松地处理基础数据。如果您仍然要编写原始SQL,那么在那里进行计算就没有问题,如果格式正确,您的SQL也会看起来更好,更易于阅读。


0

至关重要的是,“性能”没有定义。

对我来说最重要的是开发人员时间。

编写SQL查询。如果速度太慢或数据库成为瓶颈,请重新考虑。到那时,您将能够对这两种方法进行基准测试,并根据与设置相关的真实数据(硬件以及所使用的任何堆栈)做出决定。


0

我不认为没有特定的示例和基准就无法推断出性能差异,但是我有另一种看法:

您能更好地维护哪个?例如,您可能希望将前端从Java切换到Flash,HTML5,C ++或其他。大量程序经历了这种变化,或者甚至以一种以上的语言存在,因为它们需要在多个设备上工作。

即使您有一个适当的中间层(从给出的示例来看,事实并非如此),该层也可能会更改,并且JBoss可能会变成Ruby / Rails。

另一方面,不太可能用非关系数据库的SQL替换SQL后端,即使这样做,也无论如何都必须从头开始重写前端,所以这很无聊。

我的想法是,如果您在数据库中进行计算,那么稍后再编写第二个前端或中间层会容易得多,因为您不必重新实现所有操作。但是在实践中,我认为“人们可以理解的代码在哪里可以实现”是最重要的因素。


如果您从jboss更改为ruby,则很可能会更改db(并且无论如何都需要采用这些计算方法),并且更改为nosql的可能性也不太可能。
Dainius

0

为了简化如何回答这一问题,将着眼于负载平衡。您希望将负载放在容量最大的位置(如果有任何意义)。在大多数系统中,是SQL Server迅速成为瓶颈,因此可能的答案是您不希望SQL比其做更多的工作。

同样,在大多数体系结构中,构成系统核心和外部系统的SQL Server也是如此。

但是上面的数学运算是如此微不足道,除非您将系统推到极限,否则放置它的最佳位置就是您想要放置它的位置。如果数学不是很简单的,例如计算sin / cos / tan,例如进行距离计算,那么努力可能就变得很简单,需要仔细计划和测试。


0

这个问题的其他答案很有趣。令人惊讶的是,没有人回答您的问题。您在想:

  1. 在查询中转换为美分更好吗?我认为转换为美分不会在查询中添加任何内容。
  2. 在查询中使用now()更好吗?我希望将日期传递到查询中,而不是在查询中计算日期。

更多信息:对于问题一,您要确保汇总分数的过程中没有舍入错误。我认为数字19,2对于金钱来说是合理的,在第二种情况下,整数可以。因此,使用浮动货币是错误的。

对于第二个问题,我希望以程序员的身份完全控制什么日期被视为“现在”。使用now()之类的功能时,很难编写自动的单元测试。同样,当事务脚本较长时,最好将一个变量设置为now()并使用该变量,以便所有逻辑使用完全相同的值。


0

让我以一个真实的例子来解决这个问题

我需要根据自己的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通过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版本的加权平均运行速度还要快!我一步一步地编写了该查询,所以挂在那里,你会做的很好

速度

0.42141127300055814秒Python

0.23801879299935536秒SQL

我的数据库中有134000个伪造的OHLC记录,分为1000只股票,所以这是SQL可以胜过您的应用服务器的一个示例


1
但是,如果您需要尽快执行数百万次操作,则生成并行python应用程序要比数据库副本容易得多。直到一定程度上,更多地依赖SQL肯定会更快/更便宜,但是最终会出现一个临界点,那就是在应用程序中进行此计算更好。
莱尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.