2013-07-23 28 views
1

您好任何想法如何加快此查询?Postgres不在性能

输入

EXPLAIN SELECT entityid FROM entity e 

LEFT JOIN level1entity l1 ON l.level1id = e.level1_level1id 
LEFT JOIN level2entity l2 ON l2.level2id = l1.level2_level2id 
WHERE 

l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f' 
AND 
(entityid NOT IN 
(1377776,1377792,1377793,1377794,1377795,1377796... 50000 ids) 
) 

输出

Nested Loop (cost=0.00..1452373.79 rows=3865 width=8) 
    -> Nested Loop (cost=0.00..8.58 rows=1 width=8) 
     Join Filter: (l1.level2_level2id = l2.level2id) 
     -> Seq Scan on level2entity l2 (cost=0.00..3.17 rows=1 width=8) 
       Filter: ((userid)::text = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f'::text) 
     -> Seq Scan on level1entity l1 (cost=0.00..4.07 rows=107 width=16) 
    -> Index Scan using fk_fk18edb1cfb2a41235_idx on entity e (cost=0.00..1452086.09 rows=22329 width=16) 
     Index Cond: (level1_level1id = l1.level1id) 

OK这里的简化版本,该连接是不是瓶颈

SELECT enitityid FROM 
(SELECT enitityid FROM enitity e LIMIT 5000) a 

WHERE 
(enitityid NOT IN 
(1377776,1377792,1377793,1377794,1377795, ... 50000 ids) 
) 

的问题是要找到enties其别没有任何这些ID

EXPLAIN

Subquery Scan on a (cost=0.00..312667.76 rows=1 width=8) 
    Filter: (e.entityid <> ALL ('{1377776,1377792,1377793,1377794, ... 50000 ids}'::bigint[])) 
    -> Limit (cost=0.00..111.51 rows=5000 width=8) 
     -> Seq Scan on entity e (cost=0.00..29015.26 rows=1301026 width=8) 
+2

**您需要向我们展示了表和索引定义**诊断慢查询需要全表和索引定义,不只是描述或意译。也许你的表格定义不好。也许索引没有正确创建。也许你没有一个你认为你做过的那个专栏的索引。没有看到表和索引定义,我们不能说。如果你知道如何做一个'EXPLAIN'或者得到一个执行计划,那就把结果也放在问题中。 –

+0

有可能是在那个NOT IN子句中的50,000个ID是什么迫使完全顺序扫描。但是我们必须看到表格定义才能知道。 –

+0

有没有关于enitity(enitityid)的索引?这是PK吗?你是否在分析餐桌? –

回答

4

一个巨大的IN列表是非常低效的。 PostgreSQL应该理想地识别它,并将它变成一个关联,它会进行反连接,但是在这一点上,查询规划者不知道如何去做,而识别这种情况所需的计划时间会花费每一个查询明智地使用NOT IN,所以它必须是一个非常低的成本检查。见this earlier much more detailed answer on the topic

正如大卫奥尔德里奇写的这个最好的解决办法是将它变成反连接。我会写它作为一个参加过VALUES名单,只是因为PostgreSQL是非常快速的解析VALUES列表成为关系,但效果是一样的:

SELECT entityid 
FROM entity e 
LEFT JOIN level1entity l1 ON l.level1id = e.level1_level1id 
LEFT JOIN level2entity l2 ON l2.level2id = l1.level2_level2id 
LEFT OUTER JOIN (
    VALUES 
    (1377776),(1377792),(1377793),(1377794),(1377795),(1377796) 
) ex(ex_entityid) ON (entityid = ex_entityid) 
WHERE l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f' 
AND ex_entityid IS NULL; 

对于一个足够大的设定值,你甚至可能是最好创建一个临时表,COPY将值加入它,创建一个PRIMARY KEY,并加入。

更多的可能性在这里探讨:

https://stackoverflow.com/a/17038097/398670

0

既然你需要,因为你的where子句检查特定用户ID “l2.userid =” 你应该做的level2entity记录你的 “LEFT JOIN level2entity” 到 “INNER JOIN level2entity”

INNER JOIN level2entity l2 ON l2.level2id = l1.level2_level2id AND l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f' 

这将希望过滤掉您的实体,这样您的NOT IN将会有更少的工作要做。

1

如果您可以重写查询以使用散列反连接,则可能会得到更好的结果。

喜欢的东西:

with exclude_list as (
    select unnest(string_to_array('1377776,1377792,1377793,1377794,1377795, ...',','))::integer entity_id) 
select entity_id 
from entity left join exclude_list on entity.entity_id = exclude_list.entity_id 
where exclude_list.entity_id is null; 
+0

我个人使用'VALUES'列表,因为它会非常有效地直接产生一个关系,或者至少'unnest'是一个'ARRAY []'构造函数,但是建立关系和做反加入是肯定的。 –