6

如果执行以下数据库(postgres)查询,则第二次调用要快得多。

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

有没有办法优化数据库以在第一次调用时快速获得结果?

第一次通话(慢)

foo3_bar_p@BAR-FOO3-Test:~$ 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)

第二次调用(快速)

  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)

foo3_text 表的表布局(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 磁盘是可能的。但也许当前的硬件也可以做出更快的结果。

版本:x86_64-unknown-linux-gnu 上的 PostgreSQL 9.1.2

如果您需要更多详细信息,请发表评论。

4

4 回答 4

4

Postgres 让您有机会在运行时查询执行时进行一些配置,以确定您的 I/O 操作优先级。

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

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

默认值为4.0,如果您的查询需要更短的时间,您可能希望增加并测试。

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

一个大但是;由于您的索引是 btree,因此您的 CPU 优先级下降的速度比 I/O 优先级上升的速度要快得多。您基本上可以将复杂性映射到优先级。

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

总而言之,这意味着,如果 Postgre 的值4.0适用于条目,您应该将其设置为100k(大约) 条目。(4.0 * log(100k) * 10M)/(log(10M) * 100k)10M

于 2014-12-04T06:52:53.917 回答
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 表示通过关系中的最后一个块预热)。返回值是预热的块数。

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

请注意,使用这些方法中的任何一种,尝试预热比缓存更多的块 - 在使用预取或读取时由操作系统或在使用缓冲区时由 PostgreSQL - 可能会导致较低编号的块被驱逐,因为更高编号的块被读取in. 预热的数据也没有受到缓存驱逐的特殊保护,因此其他系统活动可能会在读取新预热的块后不久驱逐它们;相反,预热也可能会从缓存中驱逐其他数据。由于这些原因,预热通常在启动时最有用,此时缓存大部分为空。

资源

希望这有帮助!

于 2014-12-04T14:04:15.270 回答
1

同意 Julius,但是,如果您只需要来自 foo3_beleg 的内容,请尝试使用 EXISTS(如果您也粘贴了 sql,而不仅仅是您的解释计划,这将有所帮助)。

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

但是,我怀疑您在第一遍的“唤醒”只是您的数据库将 IN 子查询行加载到内存中。无论如何,这很可能会发生,尽管 EXISTS 通常比 IN 快得多(如果不包含硬编码列表,则很少需要 IN,如果我查看 sql,则会出现黄色标志)。

于 2014-12-03T06:19:23.707 回答
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”子查询,它慢了 20 倍(第一次执行时在我的笔记本电脑上 17 秒):

  -- 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

ps 如果您好奇该数据库的用途,您可以使用它来计算任意 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
于 2014-12-03T00:04:49.610 回答