【问题标题】:SQL: When it comes to NOT IN and NOT EQUAL TO, which is more efficient and why?SQL:当谈到 NOT IN 和 NOT EQUAL TO 时,哪个更有效,为什么?
【发布时间】:2013-07-24 11:42:42
【问题描述】:

假设我有一组物品:

  • 项目1
  • 项目2
  • 项目3
  • 项目4
  • 项目5

可以通过两种方式构建查询。首先:

SELECT * 
FROM TABLE 
WHERE ITEM NOT IN ('item1', 'item2', 'item3', 'item4','item5')

或者,可以写成:

SELECT * 
FROM TABLE 
WHERE ITEM != 'item1' 
  AND ITEM != 'item2' 
  AND ITEM != 'item3' 
  AND ITEM != 'item4' 
  AND ITEM != 'item5'
  1. 哪个更高效?为什么?
  2. 什么时候一个比另一个更有效率?换句话说,如果有 500 个项目怎么办?

我的问题与 PostgreSQL 相关。

【问题讨论】:

  • 小问题:“不等于”的标准 SQL 运算符是 <>,尽管所有 (?) DBMS 似乎也支持非标准的 !=。
  • 当您说“更高效”时,您的意思是“更快”吗? “高效”可以指很多东西,而不仅仅是执行速度。
  • 高效可以指执行时间和资源使用。差不多两件事。

标签: sql postgresql


【解决方案1】:

在 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 节点,那就太好了。现在它不知道该怎么做。

另见:

【讨论】:

  • postgresql 没有具体的限制,但有些数据库对IN 运算符的大小有限制,这给AND ... <> ... 构造+1。
  • @BurhanKhalid 使用链式AND ... <> ... 也会使解析器和规划器的工作变得困难。最近有邮件列表报告称,查询计划需要 分钟 来处理包含数万个此类子句的查询,这些子句是由一些糟糕的 ORM 生成的。
  • 该死的...... *分钟* 计划?!
  • @BurhanKhalid 查询规划器必须将巨大的 AND 不等式列表合并到一个 NOT IN 列表中才能得到一个略显理智的结果,这需要时间,并且对于庞大的大多数不是疯狂的查询。这是那些“不要这样做”的事情之一......如果你正在生成一个带有 50,000 个简单的OR 子句的查询,就像我最近看到的那样,你正在做错了。几分钟的计划时间很糟糕,但是让常见案例处理奇怪的极端案例的速度要慢得多。
【解决方案2】:

我有点不同意@Jayram 最初接受的答案。

尤其重要的是,该链接适用于 SQL Server,并且与许多其他文章和答案相矛盾。此外,示例表上没有索引。

通常,对于子查询 SQL 构造

  • <>(或!=)是标量比较
  • NOT IN 是一个left-anti-semi-join关系运算符

简单来说

  • NOT IN 成为一种可以使用索引的 JOIN 形式(PostgreSQL 除外!)
  • != 通常是非 SARGable 并且可能不使用索引

这在 dba.se 上讨论过:"The use of NOT logic in relation to indexes"。对于 PostgreSQL,然后这个 explainextended article 解释了内部更多(但不幸的是,不适用于带有 NOT IN 的常量列表)。

无论哪种方式,对于常量列表,我通常会在<> 之前使用NOT IN,因为它更易于阅读并且因为@CraigRinger 解释了什么。

对于子查询,NOT EXISTS 是要走的路

【讨论】:

  • 这些都不适合 PostgreSQL;它有时可以使用<> 的索引,其中表统计信息支持排除值非常普遍的理论,并且AFAIK 它不能使用NOT IN 列表的索引,它在内部转换为id <> ALL。
  • 我的第二个链接说它与其他 RDBMS 不同。无论如何,我会使用 NOT EXISTS
  • 完全同意; NOT EXISTS 或数据列表上的反连接是理智的方式。无论如何,PostgreSQL 将not exists 变成了反连接。
  • 既然已接受的答案已更改,您可能想更改您的第一句话 ;)
猜你喜欢
  • 2011-05-09
  • 1970-01-01
  • 2021-10-12
  • 2011-03-04
  • 2015-10-13
  • 2018-02-24
  • 2019-02-02
  • 1970-01-01
  • 2019-07-15
相关资源
最近更新 更多