为什么在 SQL 查询中 NOT IN 比 IN 慢很多

Why is NOT IN much slower than IN in SQL query

我发现 IN 和 NOT IN 令人惊讶(至少对我而言)。当我尝试解释对 PostgreSQL 数据库的第一个查询时:

EXPLAIN DELETE
FROM AuditTaskImpl l
WHERE  l.processInstanceId in (select spl.processInstanceId
                               FROM ProcessInstanceLog spl
                               WHERE spl.status not in ( 2, 3))

它告诉我这个:

Delete on audittaskimpl l  (cost=2794.48..6373.52 rows=50859 width=12)
  ->  Hash Semi Join  (cost=2794.48..6373.52 rows=50859 width=12)
        Hash Cond: (l.processinstanceid = spl.processinstanceid)
        ->  Seq Scan on audittaskimpl l  (cost=0.00..2005.59 rows=50859 width=14)
        ->  Hash  (cost=1909.24..1909.24 rows=50899 width=14)
              ->  Seq Scan on processinstancelog spl  (cost=0.00..1909.24 rows=50899 width=14)
                    Filter: (status <> ALL ('{2,3}'::integer[]))

然而,当我把in换成not in时,这只是一个否定:

EXPLAIN DELETE
FROM AuditTaskImpl l
WHERE  l.processInstanceId NOT in (select spl.processInstanceId
                               FROM ProcessInstanceLog spl
                               WHERE spl.status not in ( 2, 3))

它告诉我这个:

Delete on audittaskimpl l  (cost=0.00..63321079.15 rows=25430 width=6)
  ->  Seq Scan on audittaskimpl l  (cost=0.00..63321079.15 rows=25430 width=6)
        Filter: (NOT (SubPlan 1))
        SubPlan 1
          ->  Materialize  (cost=0.00..2362.73 rows=50899 width=8)
                ->  Seq Scan on processinstancelog spl  (cost=0.00..1909.24 rows=50899 width=8)
                      Filter: (status <> ALL ('{2,3}'::integer[]))

如您所见,对于 IN,它使用散列连接,这当然要快得多,但对于 NOT IN,它仅使用简单的逐行顺序扫描。但是由于 NOT IN 只是一个否定,它可以再次使用 hash join 并做相反的事情:当嵌套 select 中有 processInstanceId 时使用 IN,将其添加到结果中,如果没有,则不添加它,带NOT IN当嵌套中有processInstanceId时select,不添加到结果中,没有时添加到结果中

那么您能解释一下为什么会这样吗?为了澄清 AuditTaskImpl 具有 processInstanceId 属性,该属性也存在于 ProcessInstanceLog table 中,尽管它们之间没有外键关系。

谢谢。

如果子查询中的 any 值为 NULLNOT IN 的语义要求返回 nothing .因此,Postgres 需要查看所有值。

我强烈建议不要将 NOT IN 与子查询一起使用。 总是 使用NOT EXISTS:

DELETE FROM AuditTaskImpl l
    WHERE NOT EXISTS (SELECT 1 
                      FROM ProcessInstanceLog spl
                      WHERE l.processInstanceId = spl.spl.processInstanceId AND
                            spl.status not in (2, 3)
                     );