2014-11-25 31 views
6

如果执行以下数据库(postgres)查询,第二个调用要快得多。提高第一个查询的性能

我想第一个查询是慢的,因为操作系统(Linux)需要从磁盘获取数据。第二个查询受益于文件系统级别和postgres中的缓存。

是否有一种方法来优化数据库以快速获取第一个调用的结果?

首先呼叫(慢)

[email protected]:~$ psql 

foo3_bar_p=# explain analyze SELECT "foo3_beleg"."id", ... FROM "foo3_beleg" WHERE 
foo3_bar_p-# (("foo3_beleg"."id" IN (SELECT beleg_id FROM foo3_text where 
foo3_bar_p(# content @@ 'footown'::tsquery)) AND "foo3_beleg"."belegart_id" IN 
foo3_bar_p(# ('...', ...)); 
                          QUERY PLAN                     
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Nested Loop (cost=75314.58..121963.20 rows=152 width=135) (actual time=27253.451..88462.165 rows=11 loops=1) 
    -> HashAggregate (cost=75314.58..75366.87 rows=5229 width=4) (actual time=16087.345..16113.988 rows=17671 loops=1) 
     -> Bitmap Heap Scan on foo3_text (cost=273.72..75254.67 rows=23964 width=4) (actual time=327.653..16026.787 rows=27405 loops=1) 
       Recheck Cond: (content @@ '''footown'''::tsquery) 
       -> Bitmap Index Scan on foo3_text_content_idx (cost=0.00..267.73 rows=23964 width=0) (actual time=281.909..281.909 rows=27405 loops=1) 
        Index Cond: (content @@ '''footown'''::tsquery) 
    -> Index Scan using foo3_beleg_pkey on foo3_beleg (cost=0.00..8.90 rows=1 width=135) (actual time=4.092..4.092 rows=0 loops=17671) 
     Index Cond: (id = foo3_text.beleg_id) 
     Filter: ((belegart_id)::text = ANY ('{... 
     Rows Removed by Filter: 1 
Total runtime: 88462.809 ms 
(11 rows) 

第二呼叫(快)的foo3_text表的

Nested Loop (cost=75314.58..121963.20 rows=152 width=135) (actual time=127.569..348.705 rows=11 loops=1) 
    -> HashAggregate (cost=75314.58..75366.87 rows=5229 width=4) (actual time=114.390..133.131 rows=17671 loops=1) 
     -> Bitmap Heap Scan on foo3_text (cost=273.72..75254.67 rows=23964 width=4) (actual time=11.961..97.943 rows=27405 loops=1) 
       Recheck Cond: (content @@ '''footown'''::tsquery) 
       -> Bitmap Index Scan on foo3_text_content_idx (cost=0.00..267.73 rows=23964 width=0) (actual time=9.226..9.226 rows=27405 loops=1) 
        Index Cond: (content @@ '''footown'''::tsquery) 
    -> Index Scan using foo3_beleg_pkey on foo3_beleg (cost=0.00..8.90 rows=1 width=135) (actual time=0.012..0.012 rows=0 loops=17671) 
     Index Cond: (id = foo3_text.beleg_id) 
     Filter: ((belegart_id)::text = ANY ('... 
     Rows Removed by Filter: 1 
Total runtime: 348.833 ms 
(11 rows) 

表布局(28M行)

foo3_egs_p=# \d foo3_text 
           Table "public.foo3_text" 
    Column |   Type   |       Modifiers       
----------+-----------------------+------------------------------------------------------------ 
id  | integer    | not null default nextval('foo3_text_id_seq'::regclass) 
beleg_id | integer    | not null 
index_id | character varying(32) | not null 
value | text     | not null 
content | tsvector    | 
Indexes: 
    "foo3_text_pkey" PRIMARY KEY, btree (id) 
    "foo3_text_index_id_2685e3637668d5e7_uniq" UNIQUE CONSTRAINT, btree (index_id, beleg_id) 
    "foo3_text_beleg_id" btree (beleg_id) 
    "foo3_text_content_idx" gin (content) 
    "foo3_text_index_id" btree (index_id) 
    "foo3_text_index_id_like" btree (index_id varchar_pattern_ops) 
Foreign-key constraints: 
    "beleg_id_refs_id_6e6d40770e71292" FOREIGN KEY (beleg_id) REFERENCES foo3_beleg(id) DEFERRABLE INITIALLY DEFERRED 
    "index_id_refs_name_341600137465c2f9" FOREIGN KEY (index_id) REFERENCES foo3_index(name) DEFERRABLE INITIALLY DEFERRED 

硬件变化(SSD我而不是传统磁盘)或RAM磁盘是可能的。但也许现在的硬件也可以做更快的结果。

版本:9.1.2的PostgreSQL在x86_64-未知Linux的GNU

请发表评论,如果你需要更多的细节。

+0

如果WHERE子句是永远不变的,有关定期发出请求是什么?保持数据热点是我知道的唯一避免第一次尝试的灵丹妙药。根据优化,你是否检查AND的选择性“foo3_beleg”。“belegart_id”。将它移动到第一个SELECT会变得敏感吗? – SCO 2014-11-25 16:17:56

+0

@SCO否,WHERE子句不同。搜索词(这里是'footown')不同。 – guettli 2014-11-25 20:16:07

+2

的Postgres 9.4将有'pg_prewarm'扩展,它可以填补需求缓冲区高速缓存:http://www.postgresql.org/docs/9.4/static/pgprewarm.html – 2014-11-27 15:46:47

回答

0

有时,将“WHERE x IN”移动到JOIN中可显着提高性能。试试这个:

SELECT 
    foo3_beleg.id, ... 
FROM 
    foo3_beleg b INNER JOIN 
    foo3_text t ON (t.beleg_id = b.id AND t.content @@ 'footown'::tsquery) 
WHERE 
    foo3_beleg.belegart_id IN ('...', ...); 

这里有一个可重复的实验来支持我的要求。

我碰巧有一个很大的Postgres数据库(3000万行)(http://juliusdavies.ca/2013/j.emse/bertillonage/),所以我把它加载到postgres 9.4beta3中。

结果令人印象深刻。子选择的方法是大约20倍慢:

time psql myDb < using-in.sql 
real 0m17.212s 

time psql myDb < using-join.sql 
real 0m0.807s 

对于那些有兴趣在复制,这里是我用来测试我的理论的原始的SQL查询。

此查询使用一个“SELECT IN”子查询,它是较慢的(我的笔记本电脑上的第一执行17秒)20次:

-- using-in.sql 
    SELECT 
    COUNT(DISTINCT sigsha1re) AS a_intersect_b, infilesha1 
    FROM 
    files INNER JOIN sigs ON (files.filesha1 = sigs.filesha1) 
    WHERE 
    sigs.sigsha1re IN (
     SELECT sigsha1re FROM sigs WHERE sigs.sigsha1re like '0347%' 
    ) 
    GROUP BY 
    infilesha1 

此查询移动条件出的子查询并进入加入标准,速度提高20倍(第一次执行时笔记本电脑上的时间为0.8秒)。

-- using-join.sql 
    SELECT 
    COUNT(DISTINCT sigsha1re) AS a_intersect_b, infilesha1 
    FROM 
    files INNER JOIN sigs ON (
     files.filesha1 = sigs.filesha1 AND sigs.sigsha1re like '0347%' 
    ) 
    GROUP BY 
    infilesha1 

附:如果你对数据库的用途感到好奇,你可以用它来计算一个任意的jar文件与2011年左右的maven版本库中的所有jar文件的相似程度。

./query.sh lib/commons-codec-1.5.jar | psql myDb 

similarity |      a = 39 = commons-codec-1.5.jar (bin2bin)      
------------+-------------------------------------------------------------------------------------- 
    1.000  | commons-codec-1.5.jar 
    0.447  | commons-codec-1.4.jar 
    0.174  | org.apache.sling.auth.form-1.0.2.jar 
    0.170  | org.apache.sling.auth.form-1.0.0.jar 
    0.142  | jbehave-core-3.0-beta-3.jar 
    0.142  | jbehave-core-3.0-beta-4.jar 
    0.141  | jbehave-core-3.0-beta-5.jar 
    0.141  | jbehave-core-3.0-beta-6.jar 
    0.140  | commons-codec-1.2.jar 
+0

“有时,将”WHERE x IN“移动到JOIN中可显着提高性能。” - 你一定在谈论MySQL。 Postgres足够聪明,可以重写查询树。 OP的问题显然与“索引/数据不在缓存中”的问题有关。 (和正确的答案是,我会收集,已经在征求意见。) – 2014-12-03 10:44:22

+1

什么时候拿到的Postgres在这更好的?我遇到了8.4和9.0的这个问题。从未尝试过其他版本。 (和我有类似的症状:慢查询第一次,更快的第二次。) – 2014-12-03 16:56:47

+0

它已经这样做了,只要我使用的是Postgres了(8.0);多年来一直在改进的地方是哪些额外的子查询类型崩溃了。 – 2014-12-04 08:57:14

1

同意朱利叶斯,但是,如果你只需要的东西从foo3_beleg,尝试,而不是存在(如果您想粘贴你的SQL也是如此,不只是你的解释计划,这将有助于)。

select ... 
from foo3_beleg b 
where exists 
(select 1 from foo_text s where t.beleg_id = b.id) 
.... 

不过,我怀疑你的一号通“唤醒”只是你的数据库加载了IN子行到内存中。尽管一个EXISTS通常比一个IN(INs很少需要,如果不包含硬编码列表,并且如果我查看sql时会出现一个黄色标记),那很可能会发生。

+0

使用存在对此无济于事。 OP的问题显然与“索引/数据不在缓存中”的问题有关。 (正确的答案是,我会收集,已经在评论中了。) – 2014-12-03 10:46:32

+0

好吧,如果你注意到了,我几乎可以说“......可能发生,无论......”。另外,根据实际数据的外观以及pg如何加载缓存/检查匹配,请记住一个EXIST是快速的,因为它可以在第一次匹配时自由返回。 IN也可能是这种情况,但这是一个优化器方面,而不是SQL命令的一个特性。底线:我还没有遇到IN 的有效用例,它不能是EXISTS。 – 2014-12-04 19:11:12

2

Postgres为您提供了一个在执行运行时执行一些配置的机会,以决定您的I/O操作优先级。

random_page_cost(floating point) -(reference)是什么可以帮助你。它将基本上设置您的IO/CPU操作比率。

更高的值意味着I/O很重要,我有顺序磁盘;而较低的值意味着I/O不重要,我有随机存取磁盘。

默认值为4.0,可能是您想增加并测试您的查询是否需要更短的时间。

不要忘了,您的I/O优先级取决于您的列数,行数。

A big but;由于您的指令是btree,因此您的CPU优先级比I/O优先级上升要快得多。您基本上可以将复杂性映射到优先级。

CPU Priority = O(log(x)) 
I/O Priority = O(x) 

总而言之,这意味着,如果Postgre的价值4.0会为100k条目,你应该把它设置为10M条目(约)(4.0 * log(100k) * 10M)/(log(10M) * 100k)

+0

我很抱歉,在赏金到期之前我无法测试这个... – guettli 2014-12-05 09:12:15

+0

没问题。希望能帮助到你 :) – totten 2014-12-05 10:32:52

1

第一次执行查询时,postgres会从磁盘加载数据,即使使用优质硬盘,数据也会很慢。第二次运行查询时,它会从RAM中加载之前加载的数据,这显然会更快。

这个问题的解决方案将是关系数据加载到任何操作系统缓冲区缓存或PostgreSQL的高速缓冲存储器与:

int8 pg_prewarm(regclass, mode text default 'buffer', fork text default 'main', first_block int8 default null, last_block int8 default null)

第一个参数是将被预热的关系。第二个参数是要使用的预热方法,如下面进一步讨论的;第三个是预热的关系叉,通常是主要的。第四个参数是预热的第一个块编号(NULL被接受为零的同义词)。第五个参数是预热的最后一个块编号(NULL表示在关系中的最后一个块预热)。返回值是预热块的数量。

有三种可用的预热方法。预取会向操作系统发出异步预取请求(如果支持),否则会引发错误。读取读取请求的块的范围;与预取不同,这是同步的,并支持所有平台和构建,但可能会更慢。缓冲区将请求的块范围读入数据库缓冲区缓存。

请注意,使用这些方法中的任何一种,试图预热更多可能无法缓存的数据块 - 操作系统使用预取或读取时,或PostgreSQL使用缓冲区时 - 可能会导致较低数量的块被驱逐为更高编号块读取数据的预热也喜欢从缓存中驱逐无特殊保护,因此有可能对其他系统活动可能很快退出的新预热块被读取后。相反,预温可能会从缓存中驱逐其他数据。由于这些原因,预启动通常在启动时非常有用,因为缓存很大程度上是空的。

Source

希望这有助于!