12

任何想法如何加快这个查询?

输入

EXPLAIN SELECT entityid FROM entity e

LEFT JOIN level1entity l1 ON l1.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)

好的,这是一个简化版本,连接不是瓶颈

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

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

问题是找到没有这些 id 的实体

解释

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

4 回答 4

30

一个巨大的IN列表是非常低效的。PostgreSQL 应该理想地识别它并将其转换为它执行反连接的关系,但此时查询规划器不知道如何做到这一点,并且识别这种情况所需的规划时间将花费每个查询使用NOT IN合理,所以它必须是一个非常低成本的检查。请参阅此较早的有关该主题的更详细的答案

正如 David Aldridge 所写,最好通过将其变成反连接来解决这个问题。我会把它写成一个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将值放入其中,在其上创建 aPRIMARY KEY并在其上连接。

这里探索了更多可能性:

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

于 2013-07-24T03:38:59.593 回答
6

如果您可以重写查询以使用哈希反连接,您可能会获得更好的结果。

就像是:

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;
于 2013-07-23T15:50:48.530 回答
2

好的,我的解决方案是

  • 选择所有实体
  • left 加入在 entityid 上具有其中一个 id 的所有实体(没有 not 更快)
  • 选择连接选择为 NULL 的所有行

如中所述

http://blog.hagander.net/archives/66-Speeding-up-NOT-IN.html

于 2013-07-23T15:20:53.413 回答
0

由于您需要 level2entity 记录,因为您的 where 子句检查特定用户 ID“l2.userid =”您应该将“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 将有更少的工作要做。

于 2013-07-23T14:54:34.080 回答