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
列表,因为这会非常有效和直接地产生关系,或者至少是 unnest
和 ARRAY[]
构造函数,但无论哪种方式都可以建立关系并执行反加入它当然是正确的方法。【参考方案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“没有时区的时间戳”
AttributeError:模块 'django.contrib.postgres.fields' 没有属性 'JSONField'