Postgres 没有表现

Posted

技术标签:

【中文标题】Postgres 没有表现【英文标题】:Postgres NOT IN performance 【发布时间】:2013-07-22 17:13:37 【问题描述】:

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

输入

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)

【问题讨论】:

您需要向我们展示表和索引定义。 诊断慢查询需要完整的表和索引定义,而不仅仅是描述或解释。也许您的表格定义不佳。也许索引没有正确创建。也许您认为您在该列上没有索引。没有看到表和索引定义,我们无法判断。如果您知道如何进行EXPLAIN 或获得执行计划,请将结果也放入问题中。 很可能是那个 NOT IN 子句中的 50,000 个 ID 会强制进行完整的顺序扫描。但是我们必须看到表定义才能知道。 是否有关于实体(实体id)的索引?是PK吗?你analyze桌子了吗? 是的,它的主键 【参考方案1】:

一个巨大的IN 列表是非常低效的。 PostgreSQL 应该理想地识别它并将其转换为它执行反连接的关系,但此时查询规划器不知道如何做到这一点,并且识别这种情况所需的规划时间将花费每个查询明智地使用NOT IN,所以它必须是一个非常低成本的检查。见this earlier much more detailed answer on the topic。

正如 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将值放入其中,在其上创建一个PRIMARY KEY,然后加入该表。

这里探索了更多可能性:

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

【讨论】:

有没有办法为inet 列做同样的事情?我们有一个要在查询中排除的 IPv4 地址列表。 explain analyze SELECT d.* FROM ip_table d LEFT OUTER JOIN ( VALUES ('0.0.0.0'), ('127.0.0.1'), ('10.0.0.1'), ('255.255.255.255'),('::') ) ex(ex_entityid) ON (ip_addr in (ex_entityid)) where d.col2 = 27; 非常有帮助!我用NOT IN 进行了查询并将其变成了反JOIN。速度提高了 20 倍左右。 很高兴。对于其他读者,请注意这是 7 岁。始终检查旧答案与当前代码和行为。我不知道与此相关的任何优化,但您应该检查一下。【参考方案2】:

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

类似:

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;

【讨论】:

我个人会使用 VALUES 列表,因为这会非常有效和直接地产生关系,或者至少是 unnestARRAY[] 构造函数,但无论哪种方式都可以建立关系并执行反加入它当然是正确的方法。【参考方案3】:

好的,我的解决方案是

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

如中所述

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

【讨论】:

【参考方案4】:

由于您需要 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 将有更少的工作要做。

【讨论】:

以上是关于Postgres 没有表现的主要内容,如果未能解决你的问题,请参考以下文章

使用 node-postgres 在 utc 中获取 Postgres“没有时区的时间戳”

节点 Postgres 模块没有响应

Postgres - 没有实际的更新,有副作用吗?

为啥没有为 Postgres 视图启用行级安全性?

AttributeError:模块 'django.contrib.postgres.fields' 没有属性 'JSONField'

Postgres_FDW 没有推低 WHERE 标准