【发布时间】:2015-04-11 18:18:26
【问题描述】:
我目前正在开展一个项目,该项目将使用(最终)大约 2 亿行的 PostgreSQL 数据库,其中两个表具有有趣的关系。表 A 包含项目,其中包括字段 A.a、A.b 和 A.c,而表 B 包含 B.a、B.b、B.c 和每个字段的值 B.v。这里的目标是能够连接表 A 和 B,以便我可以获得 A 中每个条目的值 B.v。我目前将 B 设置为 B.a、B.b 和 B.c 作为具有唯一属性的多列索引。这是我能想到的表示这种关系的最佳方式(请记住,表 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 > 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 > 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
【问题讨论】:
-
当 WHERE 条件使其成为 INNER JOIN 时,为什么要使用 LEFT JOIN? C 不是可选的,LEFT JOIN 没有意义。或者当 C 结果是可选的时,将 C.p 上的 WHERE 条件移动到 LEFT JOIN 条件:ON A.user_id = C.user_id AND C.p
-
1)
left JOIN + where ...实际上是一个普通的连接(弗兰克上面所说的)2)你没有运行统计数据(估计和实际成本不同);VACUUM ANALYZE在每张桌子上。 3) 不要将列命名为 {a,b,c}
标签: sql database performance postgresql join