PostgreSQL在稀疏连接中减慢了WHERE子句

时间:2015-04-11 18:18:26

标签: sql database performance postgresql join

我目前正在开发一个项目,该项目将使用(最终)大约2亿行的PostgreSQL数据库,其中两个表有一个有趣的关系。表A包含项目,其中包括字段A.a,A.b和A.c,而表B包含B.a,B.b,B.c和每个这些的值B.v.这里的目标是能够连接表A和B,这样我就可以获得A中每个条目的值B.v.我目前使用B.a,B.b和B.c进行B设置,作为具有唯一属性的多列索引。这是我能想到的代表这种关系的最佳方式(请记住,表A可能包含数百万行,而B只包含10,000行)。如果有更好的存储方式,我很想知道。

为了使问题复杂化,我还需要来自第三张表C的一些信息,我也加入了A和B.

说明表格结构:

                        Table "A"
    Column    |   Type   | Modifiers | Storage | Stats target | Description 
--------------+----------+-----------+---------+--------------+-------------
 id           | bigint   |           | plain   |              | 
 a            | boolean  | not null  | plain   |              | 
 b            | integer  |           | plain   |              | 
 c            | smallint |           | plain   |              | 
 user_id      | bigint   |           | plain   |              | 
Indexes:
    PRIMARY KEY, btree (id)
    UNIQUE CONSTRAINT, btree (id)
    btree (user_id)
    btree (a)
    btree (b)
    btree (c)

                                                Table "B"
    Column    |   Type   |                         Modifiers                          | Storage | Stats target | Description 
--------------+----------+------------------------------------------------------------+---------+--------------+-------------
 id           | integer  | not null default nextval('A_id_seq'::regclass)             | plain   |              | 
 a            | boolean  | not null                                                   | plain   |              | 
 b            | integer  | not null                                                   | plain   |              | 
 c            | smallint | not null                                                   | plain   |              | 
 v            | bigint   | not null                                                   | plain   |              | 
Indexes:
    PRIMARY KEY, btree (id)
    UNIQUE CONSTRAINT, btree (a, b, c)
    btree (c)
    btree (b)
    btree (b, c, a)
    btree (v)
Foreign-key constraints:
    ...

                                Table "C"
    Column    |           Type           | Modifiers | Storage  | Stats target | Description 
--------------+--------------------------+-----------+----------+--------------+-------------
 user_id      | bigint                   | not null  | plain    |              | 
 p            | integer                  |           | plain    |              | 
Indexes:
    PRIMARY KEY, btree (user_id)
    UNIQUE CONSTRAINT, btree (user_id)
    btree (p)

目标是找到表A中的所有行,其中B中的对应值大于1000,C中的值大于100.目前,我的查询如下:

SELECT * FROM A
    INNER JOIN B ON
        A.a = B.a AND
        A.b = B.b AND
        A.c = B.c
    LEFT JOIN C ON
        A.user_id = C.user_id
    WHERE C.p < 100 AND B.v > 1000
    LIMIT 100;
然而,这很慢,需要几秒钟才能完成。特别地,去除B.v&gt; 1000个约束将查询从大约6秒加速到仅20毫秒。我怀疑这是因为B.v值超过1000的条目非常罕见(大约20,000以百万计)。然后我在查询中使用EXPLAIN ANALYZE子句来生成以下内容:

 Limit  (cost=0.00..1133.08 rows=100 width=215) (actual time=18.516..6571.243 rows=100 loops=1)
   ->  Nested Loop  (cost=0.00..1940860.96 rows=171291 width=215) (actual time=18.513..6571.113 rows=100 loops=1)
         ->  Nested Loop  (cost=0.00..1232018.65 rows=1171013 width=64) (actual time=9.293..6326.114 rows=3303 loops=1)
               ->  Seq Scan on A (cost=0.00..81726.86 rows=3888586 width=36) (actual time=0.018..857.948 rows=452207 loops=1)
               ->  Index Scan using [...] on B  (cost=0.00..0.28 rows=1 width=28) (actual time=0.010..0.010 rows=0 loops=452207)
                     Index Cond: (a = a AND b = b AND c = c)
                     Filter: (v > 1000)
         ->  Index Scan using user_id_pk on C  (cost=0.00..0.59 rows=1 width=151) (actual time=0.072..0.072 rows=0 loops=3303)
               Index Cond: (user_id = A.user_id)
               Filter: (p < 80)
 Total runtime: 6571.550 ms

对我来说似乎很奇怪,为查询添加一个约束需要花费更长的时间,因为与该约束匹配的数量很少(作为旁注,将其从1000减少到10,这会产生更多结果,也提供了巨大的加速)。

所以,我的问题是:数据库服务器可以不使用我在此查询中应用于B.v的索引吗?此外,是否可以以任何方式加快此查询?

感谢您的阅读,如果我犯了一个非常明显的错误,请原谅我,但到目前为止我还没能找到解决问题的方法。

修改 没有B.v&gt;的执行计划1000条款:

 Limit  (cost=0.00..13278.28 rows=100 width=215) (actual time=76.185..237.820 rows=100 loops=1)
   ->  Nested Loop  (cost=0.00..1383862.57 rows=10422 width=215) (actual time=76.183..237.739 rows=100 loops=1)
         ->  Nested Loop  (cost=0.00..1322702.97 rows=71251 width=64) (actual time=18.270..121.272 rows=840 loops=1)
               ->  Seq Scan on A  (cost=0.00..85715.82 rows=4078382 width=36) (actual time=0.037..2.520 rows=1377 loops=1)
               ->  Index Scan using [...] on B  (cost=0.00..0.28 rows=1 width=28) (actual time=0.082..0.083 rows=1 loops=1377)
                     Index Cond: (a = a AND b = b AND c = c)
         ->  Index Scan using user_id_pk on C (cost=0.00..0.85 rows=1 width=151) (actual time=0.136..0.136 rows=0 loops=840)
               Index Cond: (user_id = A.user_id)
               Filter: (C.p < 80)
 Total runtime: 238.076 ms

1 个答案:

答案 0 :(得分:0)

如果B.v上的谓词非常有选择性,那么就在该列上放置一个索引。

我怀疑使用该谓词的速度慢的原因是,只能基于访问B上的唯一索引来执行查询 - 而是需要访问表中的每一行来检查v的值同样。