在 PostgreSQL 中,在合理的列表长度上通常存在相当小的差异,尽管 IN 在概念上更清晰。很长的AND ... <> ... 列表和很长的NOT IN 列表都表现得很糟糕,AND 比NOT IN 差很多。
在这两种情况下,如果它们的长度足以让您甚至提出问题,那么您应该改为对值列表进行反连接或子查询排除测试。
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
WHERE NOT EXISTS(SELECT 1 FROM excluded e WHERE t.item = e.item);
或:
WITH excluded(item) AS (
VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT *
FROM thetable t
LEFT OUTER JOIN excluded e ON (t.item = e.item)
WHERE e.item IS NULL;
(在现代 Pg 版本上,无论如何都会产生相同的查询计划)。
如果值列表足够长(数万个项目),则查询解析可能会开始产生巨大的成本。此时,您应该考虑创建一个TEMPORARY 表,COPY 将数据排除在其中,可能在其上创建一个索引,然后在临时表上使用上述方法之一而不是 CTE。
演示:
CREATE UNLOGGED TABLE exclude_test(id integer primary key);
INSERT INTO exclude_test(id) SELECT generate_series(1,50000);
CREATE TABLE exclude AS SELECT x AS item FROM generate_series(1,40000,4) x;
其中exclude 是要省略的值列表。
然后我将相同数据的以下方法与所有结果(以毫秒为单位)进行比较:
-
NOT IN 列表:3424.596
-
AND ... 名单:80173.823
-
VALUES 基于JOIN 排除:20.727
-
VALUES 基于子查询排除:20.495
- 基于表的
JOIN,前列表中没有索引:25.183
- 基于子查询表,前列表上没有索引:23.985
...使基于 CTE 的方法比 AND 列表快三千多倍,比 NOT IN 列表快 130 倍。
这里的代码:https://gist.github.com/ringerc/5755247(请保护你的眼睛,你们谁点击这个链接)。
对于这个数据集大小,在排除列表上添加索引没有任何区别。
注意事项:
-
IN 使用SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude; 生成的列表
-
使用
SELECT string_agg(item::text, ' AND item <> ') from exclude; 生成的AND 列表
- 基于子查询和连接的表排除在重复运行时基本相同。
- 计划检查显示 Pg 将
NOT IN 转换为 <> ALL
所以...您可以看到IN 和AND 列表与正确连接之间存在真正的巨大 差距。令我惊讶的是,使用 VALUES 列表的 CTE 执行速度有多快......解析 VALUES 列表几乎没有花费时间,执行相同或略快表大多数测试中的方法。
如果 PostgreSQL 能够自动识别出荒谬的长 IN 子句或类似 AND 条件的链并切换到更智能的方法,例如进行散列连接或隐式将其转换为 CTE 节点,那就太好了。现在它不知道该怎么做。
另见: