15

我需要一个真正的 DBA 的意见。Postgres 8.3 在我的 Macbook Pro 上执行此查询需要 200 毫秒,而 Java 和 Python 在 20 毫秒(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 中仅使用切换到数字列而不是整数也会导致 10 倍的减速)

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

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 也没有从磁盘流式传输(或者你是正确的,我应该低头在谁写了他们的存储层之前!)

4

10 回答 10

15

我会说你的测试方案并不是很有用。为了完成 db 查询,db 服务器要经过几个步骤:

  1. 解析 SQL
  2. 制定查询计划,即决定使用哪些索引(如果有)、优化等。
  3. 如果使用了索引,则在其中搜索指向实际数据的指针,然后转到数据中的适当位置或
  4. 如果没有使用索引,则扫描整个表以确定需要哪些行
  5. 将数据从磁盘加载到临时位置(希望但不一定是内存)
  6. 执行 count() 和 avg() 计算

因此,在 Python 中创建一个数组并获取平均值基本上会跳过所有这些步骤,只保存最后一个。由于磁盘 I/O 是程序必须执行的最昂贵的操作之一,因此这是测试中的一个主要缺陷(另请参阅我之前在此处提出的这个问题的答案)。即使您在其他测试中从磁盘读取数据,过程也完全不同,很难判断结果的相关性。

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

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

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

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

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

更新:

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

于 2008-09-09T12:31:26.437 回答
11

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

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

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

物化视图

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

我认为这样的事情需要 200 毫秒非常好,在我的 oracle 服务器上进行快速测试,具有大约 500k 行且没有索引的相同表结构,大约需要 1 - 1.5 秒,这几乎只是 oracle 吸取数据离盘。

真正的问题是,200ms 够快吗?

- - - - - - - 更多的 - - - - - - - - - -

我对使用物化视图解决这个问题很感兴趣,因为我从来没有真正玩过它们。这是在甲骨文中。

首先,我创建了一个每分钟刷新的 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 可以让最终用户觉得它有多快,如果您可以接受不完全符合秒级精度的值。

于 2008-09-09T14:26:28.297 回答
5

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

这里的数学看起来是正确的(至少是大小,因为这就是 sqlite db 的大小:-)

我只是不购买磁盘原因缓慢的论点,因为有所有迹象表明这些表都在内存中(postgres 的人都警告不要过于努力地将表固定到内存,因为他们发誓操作系统会比程序员做得更好)

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

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

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

于 2008-09-10T02:29:45.767 回答
3

这些是非常详细的答案,但它们大多是在回避问题,考虑到数据很容易放入内存、需要并发读取但不需要写入并且一遍又一遍地使用相同的查询进行查询,我如何在不离开 Postgres 的情况下获得这些好处。

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

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

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

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

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

更新:我认为通过将 350,000 通过驱动程序和序列化步骤拉入 Python 以运行聚合来比较 SELECTS 是不公平的,甚至也不能省略聚合,因为格式化和显示的开销很难与定时。如果两个引擎都在内存数据中运行,那应该是苹果对苹果的比较,但我不确定如何保证这已经发生。

我不知道如何添加评论,也许我没有足够的声誉?

于 2008-09-09T13:50:18.477 回答
3

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

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

信封计算的快速回溯让我怀疑您正在从磁盘分页。假设 Postgres 使用 4 字节整数,则每行有 (6 * 4) 字节,因此您的表最小为 (24 * 350,000) 字节 ~ 8.4MB。假设您的 HDD 上的持续吞吐量为 40 MB/s,您正在查看大约 200 毫秒来读取数据(正如所指出的,这应该是几乎所有时间都花在的地方)。

除非我在某个地方搞砸了我的数学,否则我看不出您如何能够将 8MB 读入您的 Java 应用程序并在您显示的时间处理它 - 除非该文件已经被驱动器或您的操作系统。

于 2008-09-10T01:47:32.590 回答
1

我认为您的结果并不令人惊讶——如果有的话,Postgres 是如此之快。

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

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

于 2008-09-09T11:43:10.967 回答
1

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

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

于 2008-09-09T13:04:15.077 回答
1

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

于 2008-09-10T09:45:20.150 回答
0

您需要将 postgres 的缓存增加到整个工作集适合内存的程度,然后才能期望看到与使用程序在内存中执行它的性能相当。

于 2008-09-09T14:10:06.667 回答
0

感谢甲骨文的时间安排,这就是我正在寻找的东西(虽然令人失望:-)

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

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

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

最后我大概可以忍受 200 毫秒的响应时间,但是知道 7 毫秒是一个可能的目标让我感到不满意,因为即使是 20-50 毫秒的时间也会让更多的用户有更多的最新查询并摆脱许多缓存和预先计算的黑客。

我刚刚使用 MySQL 5 检查了时间,它们比 Postgres 稍差。所以除非有一些重大的缓存突破,我想这就是我可以期待的关系数据库路线。

我希望我能投票赞成你的一些答案,但我还没有足够的分数。

于 2008-09-09T15:34:19.943 回答