使用 OR 的 SQL 查询比 2 个单独的查询慢得多

Posted

技术标签:

【中文标题】使用 OR 的 SQL 查询比 2 个单独的查询慢得多【英文标题】:SQL query with OR much slower than 2 separate queries 【发布时间】:2018-04-07 20:56:04 【问题描述】:

当我解释以下查询时:

EXPLAIN DELETE
FROM AuditTaskImpl l
WHERE l.processInstanceId IN (SELECT spl.processInstanceId
                              FROM ProcessInstanceLog spl
                              WHERE spl.status IN (2,3))
      OR NOT EXISTS (SELECT spl.processInstanceId
                     FROM ProcessInstanceLog spl
                     WHERE l.processinstanceid = spl.processinstanceid);

它产生:

Delete on audittaskimpl l  (cost=8.61..424652.49 rows=38144 width=6)
  ->  Seq Scan on audittaskimpl l  (cost=8.61..424652.49 rows=38144 width=6)
        Filter: ((hashed SubPlan 1) OR (NOT (SubPlan 2)))
        SubPlan 1
          ->  Index Scan using idx_pinstlog_status on processinstancelog spl  (cost=0.29..8.61 rows=1 width=8)
                Index Cond: (status = ANY ('2,3'::integer[]))
        SubPlan 2
          ->  Index Only Scan using idx_pinstlog_pinstid on processinstancelog spl_1  (cost=0.29..8.31 rows=1 width=0)
                Index Cond: (processinstanceid = l.processinstanceid)

所以大约 400k 获取。但是由于我使用了 OR,理论上我可以分别运行这两个查询,然后将它们合并。那么第一个:

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

产生:

Delete on audittaskimpl l  (cost=8.62..2147.72 rows=1 width=12)
  ->  Hash Semi Join  (cost=8.62..2147.72 rows=1 width=12)
        Hash Cond: (l.processinstanceid = spl.processinstanceid)
        ->  Seq Scan on audittaskimpl l  (cost=0.00..2005.59 rows=50859 width=14)
        ->  Hash  (cost=8.61..8.61 rows=1 width=14)
              ->  Index Scan using idx_pinstlog_status on processinstancelog spl  (cost=0.29..8.61 rows=1 width=14)
                    Index Cond: (status = ANY ('2,3'::integer[]))

第二个:

EXPLAIN DELETE
FROM AuditTaskImpl l
WHERE NOT EXISTS (SELECT spl.processInstanceId
                  FROM ProcessInstanceLog spl
                  WHERE l.processinstanceid = spl.processinstanceid);

产生:

Delete on audittaskimpl l  (cost=2666.49..5736.94 rows=1 width=12)
  ->  Hash Anti Join  (cost=2666.49..5736.94 rows=1 width=12)
        Hash Cond: (l.processinstanceid = spl.processinstanceid)
        ->  Seq Scan on audittaskimpl l  (cost=0.00..2005.59 rows=50859 width=14)
        ->  Hash  (cost=1781.66..1781.66 rows=50866 width=14)
              ->  Seq Scan on processinstancelog spl  (cost=0.00..1781.66 rows=50866 width=14)

所以总共 cca 8k 磁盘获取。 这两个表都包含 cca 50 000 行。数据库是 PostgreSQL 9.3。示例是使用 DML (DELETE FROM ...),但使用 DQL (SELECT...) 会产生相同的结果。

这里的另一个例子是使用 UNION ALL 的 SELECT:

EXPLAIN SELECT l.id
FROM AuditTaskImpl l
WHERE NOT EXISTS (SELECT spl.processInstanceId
                  FROM ProcessInstanceLog spl
                  WHERE l.processinstanceid = spl.processinstanceid)

UNION ALL

SELECT l.id
FROM AuditTaskImpl l
WHERE l.processInstanceId IN (SELECT spl.processInstanceId
                              FROM ProcessInstanceLog spl
                              WHERE spl.status IN (2,3))

产生:

Append  (cost=2616.49..7975.41 rows=2 width=8)
  ->  Hash Anti Join  (cost=2616.49..5827.67 rows=1 width=8)
        Hash Cond: (l.processinstanceid = spl.processinstanceid)
        ->  Seq Scan on audittaskimpl l  (cost=0.00..2005.59 rows=50859 width=16)
        ->  Hash  (cost=1781.66..1781.66 rows=50866 width=8)
              ->  Seq Scan on processinstancelog spl  (cost=0.00..1781.66 rows=50866 width=8)
  ->  Hash Semi Join  (cost=8.62..2147.72 rows=1 width=8)
        Hash Cond: (l_1.processinstanceid = spl_1.processinstanceid)
        ->  Seq Scan on audittaskimpl l_1  (cost=0.00..2005.59 rows=50859 width=16)
        ->  Hash  (cost=8.61..8.61 rows=1 width=8)
              ->  Index Scan using idx_pinstlog_status on processinstancelog spl_1  (cost=0.29..8.61 rows=1 width=8)
                    Index Cond: (status = ANY ('2,3'::integer[]))

所以总共获取了 8k 的 cca。为什么带有 OR 的 SQL 查询比 2 个单独的查询慢得多?可能是优化器问题?

感谢回复!

【问题讨论】:

你的观点是什么? OR 难以优化,可能导致查询计划不理想。 我的观点是为什么会发生这种情况,因为我认为如何优化它很明显。这是一个相对简单的查询,我已经看到更困难的查询优化得更好,所以我对此感到非常惊讶:) 。 . Postgres 是开源的。随时协助改进优化器。 【参考方案1】:

一个查询就够了,为什么还要浪费时间处理两个查询?

DELETE
  FROM AuditTaskImpl l
  WHERE not exists (
     SELECT null FROM ProcessInstanceLog spl
       WHERE spl.processInstanceId = l.processInstanceId
         and spl.status not IN (2,3))

【讨论】:

你的其实挺好的:在 audittaskimpl l 上删除 (cost=2793.65..5864.11 rows=1 width=12) -> Hash Anti Join (cost=2793.65..5864.11 rows=1 width= 12) Hash Cond: (l.processinstanceid = spl.processinstanceid) -> Seq Scan on audittaskimpl l (cost=0.00..2005.59 rows=50859 width=14) -> Hash (cost=1908.83..1908.83 rows=50866 width= 14) -> Seq Scan on processinstancelog spl (cost=0.00..1908.83 rows=50866 width=14) Filter: (status ALL ('2,3'::integer[])) 你是怎么做到的这么快就找到了? :) 我以此为生。这并不快:半夜在平板电脑上打字。

以上是关于使用 OR 的 SQL 查询比 2 个单独的查询慢得多的主要内容,如果未能解决你的问题,请参考以下文章

数据库 | SQL语法优化方法及实例详解

为啥 PLSQL 比 SQL*Plus 慢

mysql如何找出慢sql

Mysql中如何查看慢查询以及查看线程

mysql 慢日志怎么按时间查询

为啥通过 django QuerySet 进行查询比在 Django 中使用游标慢得多?