【问题标题】:PostgreSQL: NOT IN versus EXCEPT performance difference (edited #2)PostgreSQL:NOT IN 与 EXCEPT 性能差异(编辑 #2)
【发布时间】:2011-10-30 19:36:32
【问题描述】:

我有两个功能相同的查询。其中一个表现非常好,另一个表现很差。我看不出性能差异是从哪里产生的。

查询 #1:

SELECT id 
FROM subsource_position
WHERE
  id NOT IN (SELECT position_id FROM subsource)

这会带来以下计划:

                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Seq Scan on subsource_position  (cost=0.00..362486535.10 rows=128524 width=4)
   Filter: (NOT (SubPlan 1))
   SubPlan 1
     ->  Materialize  (cost=0.00..2566.50 rows=101500 width=4)
           ->  Seq Scan on subsource  (cost=0.00..1662.00 rows=101500 width=4)

查询 #2:

SELECT id FROM subsource_position
EXCEPT
SELECT position_id FROM subsource;

计划:

                                           QUERY PLAN                                            
-------------------------------------------------------------------------------------------------
 SetOp Except  (cost=24760.35..25668.66 rows=95997 width=4)
   ->  Sort  (cost=24760.35..25214.50 rows=181663 width=4)
         Sort Key: "*SELECT* 1".id
         ->  Append  (cost=0.00..6406.26 rows=181663 width=4)
               ->  Subquery Scan on "*SELECT* 1"  (cost=0.00..4146.94 rows=95997 width=4)
                     ->  Seq Scan on subsource_position  (cost=0.00..3186.97 rows=95997 width=4)
               ->  Subquery Scan on "*SELECT* 2"  (cost=0.00..2259.32 rows=85666 width=4)
                     ->  Seq Scan on subsource  (cost=0.00..1402.66 rows=85666 width=4)
(8 rows)

我有一种感觉,要么我的某个查询遗漏了一些明显不好的地方,要么我错误地配置了 PostgreSQL 服务器。我本来希望这个NOT IN 能很好地优化; NOT IN 总是性能问题还是有原因没有在这里优化?

补充资料:

=> select count(*) from subsource;
 count 
-------
 85158
(1 row)

=> select count(*) from subsource_position;
 count 
-------
 93261
(1 row)

编辑:我现在已经修复了下面提到的 A-B != B-A 问题。但是我所说的问题仍然存在:查询#1仍然比查询#2差很多。我相信这是因为两个表的行数相似。

编辑 2:我正在使用 PostgresQL 9.0.4。我不能使用 EXPLAIN ANALYZE,因为查询 #1 花费的时间太长。所有这些列都不是 NULL,因此应该没有区别。

编辑 3:我对这两列都有索引。我还没有完成查询#1(大约 10 分钟后放弃)。查询 #2 立即返回。

【问题讨论】:

  • @mu 太短:这正是问题所在。不同的查询语义。你应该这样回答
  • 我已经更正了;请再看看。
  • 您需要指定您使用的 PostgreSQL 版本。另外,我现在也不相信这些查询是相同的 - 考虑 NULL 值。最后,您应该始终发布 EXPLAIN ANALYZE 而不仅仅是简单的 EXPLAIN。
  • NOT IN 就性能而言通常是个坏主意。这可能是 PostgreSQL 拥有(可能是专有的,因为我在其他 RDBMS 中从未听说过它)除了。您没有说查询实际上是否需要不同的时间来执行,或者它是否只是估计的成本(这可能是相当低的)。那么索引呢?
  • @Marian 仅供参考,MS SQL Server 和 IBM DB2 有 EXCEPT,Oracle 有 MINUS。我猜其他数据库也有它们的等价物......

标签: sql postgresql


【解决方案1】:

查询 #1 不是执行此操作的优雅方式...(NOT)IN SELECT 适用于一些条目,但它不能使用索引(Seq Scan)。

没有 EXCEPT,替代方法是使用 JOIN (HASH JOIN):

    SELECT sp.id
    FROM subsource_position AS sp
        LEFT JOIN subsource AS s ON (s.position_id = sp.id)
    WHERE
        s.position_id IS NULL

EXCEPT 很久以前就出现在 Postgres 中了...但是使用 MySQL 我相信这仍然是唯一的方法,使用索引来实现这一点。

【讨论】:

  • 好主意!
【解决方案2】:

由于您使用默认配置运行,请尝试提高 work_mem。最有可能的是,子查询最终会被假脱机到磁盘,因为您只允许 1Mb 的工作内存。试试 10 或 20mb。

【讨论】:

  • 在我的测试中,低至 2 MB 的值足以为我解决此问题。我可能会在两者之间使用一些东西。感谢您的帮助!
  • 性能差异与服务器配置无关。这两个查询在使用索引方面具有不同的能力(或限制)。 #1 => 需要一一扫描子源中的所有条目,#2 可以让两个索引都“相互匹配”(HASH JOIN)。但是好的,为“在 RAM 中扫描表”添加更多“工作内存”可以帮助运行非常糟糕的 SQL,而不使用联结...
【解决方案3】:

您的查询在功能上并不等效,因此对其查询计划的任何比较都是没有意义的。

您的第一个查询,用集合论的术语来说,是这样的:

{subsource.position_id} - {subsource_position.id}
          ^        ^                ^        ^

但你的第二个是这样的:

{subsource_position.id} - {subsource.position_id}
          ^        ^                ^        ^

对于任意集合 ABA - BB - A 不同。

将您的查询修正为语义等价,然后重试。

【讨论】:

  • 我已经更正了 SQL 和查询计划。如果您再看一看,我将不胜感激。
  • 哇,我必须阅读大约 5 次才能看到“。”位置的差异。和“_”字符。
  • @RossBradbury 这个小小的编辑是否让它更清晰?
【解决方案4】:

如果idposition_id 都被索引(在它们自己的或多列索引中的第一列上),那么两次索引扫描都是必要的——它是一个简单的基于排序合并的集合算法。

我个人认为 PostgreSQL 根本没有优化智能来理解这一点。

(我在诊断出一个查询运行超过 24 小时后提出了这个问题,我可以在几秒钟内在命令行上使用 sort x y y | uniq -u 执行该查询。使用 pg_dump 导出时数据库小于 50MB。)

PS:更多有趣的评论here

已在优化方面投入更多工作 EXCEPT 和 NOT EXISTS 比 NOT IN,因为后者实质上是 由于其不直观但规范要求的 NULL 处理,因此不太有用。 我们不会为此道歉,也不会将其视为错误。

归结为exceptnot in 在空处理方面不同。我没有查看细节,但这意味着PostgreSQL(积极地)没有优化它。

【讨论】:

    【解决方案5】:

    第二个查询利用了 postgresql 的 HASH JOIN 特性。这比第一个的Seq Scan 快得多。

    【讨论】:

    • 这是否意味着'not in'不能使用索引?
    猜你喜欢
    • 2019-03-09
    • 1970-01-01
    • 1970-01-01
    • 2018-09-28
    • 2015-08-06
    • 2020-04-19
    • 2021-07-25
    • 2021-02-03
    • 2012-02-11
    相关资源
    最近更新 更多