为什么SQL聚合函数比Python和Java(或穷人的OLAP)慢得多

时间:2008-09-09 10:33:40

标签: python sql optimization aggregate olap

我需要一个真正的DBA意见。 Postgres 8.3在我的Macbook Pro上执行此查询需要200 ms,而Java和Python在20 ms(350,000行)内执行相同的计算:

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

使用SQL数据库时这是正常行为吗?

架构(该表包含对调查的回复):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

我用Java和Python编写了一些测试用于上下文,他们粉碎了SQL(纯Python除外):

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

即使sqlite3与Postgres竞争,尽管它假设所有列都是字符串(相比之下:即使只使用切换到数字列而不是Postgres中的整数导致10x减速)

我试过没有成功的调整包括(盲目地遵循一些网络建议):

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

所以我的问题是,我的经验是否正常,这是我在使用SQL数据库时可以期待的?我可以理解ACID必须带来成本,但在我看来这有点疯狂。我不是要求实时游戏速度,但由于Java可以在20毫秒内处理数百万的双打,我感到有点嫉妒。

是否有更好的方法可以廉价地进行简单的OLAP(无论是在金钱和服务器复杂性方面)?我已经调查了Mondrian和Pig + Hadoop,但对于维护另一台服务器应用程序并不确定它们是否会提供帮助并不是非常兴奋。


没有Python代码和Java代码可以完成内部的所有工作。我只生成4个阵列,每个阵列有350,000个随机值,然后取平均值。我不在时间中包括生成,只包括平均步骤。 java线程计时使用4个线程(每个阵列平均一个),过度杀伤但它绝对是最快的。

sqlite3时序由Python程序驱动,并从磁盘运行(不是:内存:)

我意识到Postgres在幕后做得更多,但大部分工作对我来说并不重要,因为这是只读数据。

Postgres查询不会改变后续运行的时间。

我重新运行Python测试以包括将其从磁盘中删除。时间大大减慢到近4秒。但我猜测Python的文件处理代码几乎是在C语言中(尽管可能不是csv lib?)所以这向我表明Postgres也没有从磁盘流式传输(或者说你是正确的我应该鞠躬在谁写了他们的存储层之前!)

10 个答案:

答案 0 :(得分:14)

我会说你的测试方案并不真正有用。要完成db查询,db服务器将执行以下几个步骤:

  1. 解析SQL
  2. 制定查询计划,i。即决定使用哪些指数(如果有的话),优化等。
  3. 如果使用索引,搜索指向实际数据的指针,然后转到数据中的适当位置或
  4. 如果没有使用索引,请扫描整个表以确定需要哪些行
  5. 将数据从磁盘加载到临时位置(希望但不一定是内存)
  6. 执行count()和avg()计算
  7. 因此,在Python中创建一个数组并获得平均值基本上会跳过所有这些步骤,保存最后一个。由于磁盘I / O是程序必须执行的最昂贵的操作之一,这是测试中的一个主要缺陷(另请参阅我之前提到的this question的答案)。即使您在其他测试中从磁盘读取数据,该过程也完全不同,并且很难判断结果的相关性。

    为了获得有关Postgres花费时间的更多信息,我建议进行以下测试:

    • 将查询的执行时间与没有聚合函数的SELECT进行比较(即剪切步骤5)
    • 如果您发现聚合导致显着减速,请尝试Python更快地执行此操作,通过比较中的普通SELECT获取原始数据。

    要加快查询速度,请先减少磁盘访问。我非常怀疑这是花费时间的聚合。

    有几种方法可以做到这一点:

    • 通过数据库引擎自身的功能或使用memcached等工具缓存数据(在内存中!)以供后续访问;
    • 缩小存储数据的大小
    • 优化指数的使用。有时这可能意味着完全跳过索引使用(毕竟,它也是磁盘访问)。对于MySQL,我似乎记得如果你假设查询占据表中所有数据的10%以上,建议跳过索引。
    • 如果您的查询充分利用索引,我知道对于MySQL数据库,它有助于将索引和数据放在不同的物理磁盘上。但是,我不知道这是否适用于Postgres。
    • 还可能存在更复杂的问题,例如,如果由于某种原因结果集无法在内存中完全处理,则将行交换到磁盘。但是我会留下这种研究,直到我遇到严重的性能问题,我找不到另一种方法来修复,因为它需要了解你的过程中很多一些不太引人注目的细节。

    更新

    我刚刚意识到你似乎没有使用上述查询的索引,而且很可能也没有使用任何索引,所以我对索引的建议可能没有帮助。抱歉。不过,我会说聚合不是问题,但磁盘访问是。我会留下索引的东西,无论如何,它可能还有一些用处。

答案 1 :(得分:8)

Postgres做得比看起来要多得多(保持数据一致性!)

如果值不必是100%点,或者表很少更新,但是您经常运行此计算,则可能需要查看物化视图以加快速度。

(注意,我没有在Postgres中使用物化视图,他们看起来有点hacky,但可能适合你的情况。)

Materialized Views

还要考虑实际连接到服务器的开销以及将请求发送到服务器并返回所需的往返。

我认为200ms对于这样的东西来说非常好,在我的oracle服务器上进行快速测试,相同的表结构有大约500k行且没有索引,需要大约1 - 1.5秒,这几乎都是oracle从磁盘上吸取数据。

真正的问题是,200毫秒足够快吗?

--------------更多--------------------

我有兴趣使用物化视图来解决这个问题,因为我从未真正使用它们。这是在oracle。

首先,我创建了一个每分钟刷新一次的MV。

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

虽然它令人耳目一新,但没有返回行

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

一旦刷新,它比原始查询更快

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

如果我们插入基表,结果不能立即查看MV。

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

但是等一下左右,MV会在幕后更新,结果可以快速返回。

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

这不太理想。一开始,它不是实时的,插入/更新将不会立即可见。此外,无论您是否需要,您都可以运行查询来更新MV(这可以调整到任何时间范围或按需)。但是,这确实表明MV可以让最终用户看起来更快,如果你可以使用的值不是第二准确值。

答案 2 :(得分:5)

我重新测试了MySQL指定ENGINE = MEMORY并且它没有改变任何东西(仍然是200毫秒)。使用内存数据库的Sqlite3也提供了类似的时序(250毫秒)。

数学here看起来是正确的(至少是大小,因为sqlite数据库有多大: - )

我只是没有购买disk-cause-slowness参数,因为每个迹象表明这些表都在内存中(postgres的所有人都警告不要过于努力将表格固定到内存中,因为他们发誓操作系统会做得更好而不是程序员)

为了澄清时间,Java代码不是从磁盘读取,如果Postgres从磁盘读取并计算复杂的查询,那么这是一个完全不公平的比较,但是除此之外,数据库应该足够聪明到将一个小表放入内存并预编译存储过程恕我直言。

更新(回应下面的第一条评论):

我不确定如何在不使用聚合函数的情况下以一种公平的方式测试查询,因为如果我选择所有行,它将花费大量时间序列化和格式化所有内容。我并不是说缓慢是由聚合函数引起的,它可能只是来自并发性,完整性和朋友的开销。我只是不知道如何将聚合分离为唯一的自变量。

答案 3 :(得分:3)

这些是非常详细的答案,但他们大多提出这个问题,如果数据很容易适应内存,需要并发读取但不需要写入,并且一遍又一遍地查询相同的查询,如何在不离开Postgres的情况下获得这些好处试。

是否可以预编译查询和优化计划?我原以为存储过程会这样做,但它并没有真正帮助。

为了避免磁盘访问,有必要将整个表缓存到内存中,我可以强制Postgres这样做吗?我认为它已经这样做了,因为重复运行后查询只在200毫秒内执行。

我可以告诉Postgres该表是只读的,所以它可以优化任何锁定代码吗?

我认为用空表估计查询构建成本是可能的(时间范围为20-60毫秒)

我仍然不明白为什么Java / Python测试无效。 Postgres只是没有做更多的工作(虽然我还没有解决并发方面,只是缓存和查询构造)

更新: 我认为通过将350,000通过驱动程序和序列化步骤拉到Python中来运行聚合来比较SELECTS是不公平的,甚至也不会忽略聚合,因为格式化和显示的开销很难与时间分离。如果两个引擎都在内存数据中运行,它应该是一个苹果对苹果的比较,我不知道如何保证已经发生了这种情况。

我无法弄清楚如何添加评论,也许我没有足够的声誉?

答案 4 :(得分:2)

我自己就是MS-SQL的人,我们使用DBCC PINTABLE来保持表的缓存,SET STATISTICS IO看看它是从缓存中读取的,而不是磁盘。

我在Postgres上找不到任何模仿PINTABLE的内容,但是pg_buffercache似乎提供了缓存中的内容的详细信息 - 你可能想检查一下,看看你的表是否真的被缓存了。 / p>

快速回退信封计算会让我怀疑你是从磁盘分页。假设Postgres使用4字节整数,则每行有(6 * 4)个字节,因此您的表最小为(24 * 350,000)字节~8.4MB。假设你的硬盘上有40 MB / s的持续吞吐量,那么你正在寻找大约200ms的时间来读取数据(as pointed out应该是几乎所有时间花在的地方)。

除非我在某个地方搞砸了数学,否则我看不出你能够在你的Java应用程序中读取8MB并在你显示的时候处理它是多么可能 - 除非该文件已被任何一个缓存开车或你的操作系统。

答案 5 :(得分:1)

我认为你的结果并不令人惊讶 - 如果说Postgres的速度如此之快。

一旦有机会缓存​​数据,Postgres查询是否会再次运行得更快?为了更加公平,您对Java和Python的测试应首先涵盖获取数据的成本(理想情况下将其从磁盘上加载)。

如果这个性能级别在实践中对您的应用程序有问题,但出于其他原因需要RDBMS,那么您可以查看memcached。然后,您可以更快地对原始数据进行缓存访问,并可以在代码中进行计算。

答案 6 :(得分:1)

您使用TCP访问Postgres吗?在那种情况下,Nagle正在搞乱你的时间。

答案 7 :(得分:0)

RDBMS通常为您做的另一件事是通过保护您免受其他进程的同时访问来提供并发性。这是通过放置锁来完成的,并且还有一些开销。

如果您正在处理永远不会发生变化的完全静态数据,特别是如果您处于基本“单用户”场景中,那么使用关系数据库并不一定会带来很多好处。

答案 8 :(得分:0)

你需要将postgres的缓存增加到整个工作集适合内存的程度,然后才能看到性能与内存中的程序相媲美。

答案 9 :(得分:0)

感谢Oracle时间,这就是我正在寻找的东西(令人失望的是: - )

物化视图可能值得考虑,因为我认为我可以为大多数用户预先计算此查询最有趣的形式。

我不认为查询往返时间应该非常高,因为我在运行Postgres的同一台机器上运行查询,所以它不会增加太多的延迟?

我也已经对缓存大小进行了一些检查,似乎Postgres依靠操作系统来处理缓存,他们特别提到BSD是理想的操作系统,所以我认为Mac OS应该非常聪明表进入记忆。除非有人考虑更具体的参数,否则我认为更具体的缓存是我无法控制的。

最后我可能忍受200毫秒的响应时间,但知道7毫秒是一个可能的目标让我感到不满意,因为甚至20-50毫秒时间将使更多的用户拥有更多的最新查询和摆脱了大量的缓存和预先计算的黑客攻击。

我刚刚使用MySQL 5检查了时间,它们比Postgres略差。因此,除了一些主要的缓存突破,我想这是我可以期待的关系数据库路由。

我希望我可以投票给你一些答案,但我还没有足够的分数。