【问题标题】:SQL EXISTS Why does selecting rownum cause inefficient execution plan?SQL EXISTS 为什么选择rownum会导致执行计划低效?
【发布时间】:2013-12-24 10:21:36
【问题描述】:

问题

我试图理解为什么这两个 Oracle 语法更新查询中看似微小的差异会导致执行计划完全不同。

查询 1:

UPDATE sales s
   SET status = 'DONE', trandate = sysdate
 WHERE EXISTS (Select *    
 FROM tempTable tmp
     WHERE s.key1 = tmp.key1
       AND s.key2 = tmp.key2
       AND s.key3 = tmp.key3)

查询 2:

UPDATE sales s
   SET status = 'DONE', trandate = sysdate
 WHERE EXISTS (Select rownum    
 FROM tempTable tmp
     WHERE s.key1 = tmp.key1
       AND s.key2 = tmp.key2
       AND s.key3 = tmp.key3)

如您所见,两者之间的唯一区别是查询 2 中的子查询返回一个 rownum 而不是每一行的值。

这两者的执行计划完全不同:

  • Query1 - 从两个表中提取总结果并使用排序和哈希连接返回结果。这执行得很好,成本为 2,346(尽管使用了 EXISTS 子句和内聚子查询)。

  • Query2 - 同时提取两个表结果,但使用计数和过滤器来完成相同的任务,并返回一个执行计划,其成本高达惊人的 77,789,696!我应该注意到他的查询只是挂在我身上,所以我实际上并不肯定这会返回相同的结果(尽管我相信它应该)。

根据我对 Exists 子句的理解,它只是一个简单的布尔检查,在主表的每一行运行。如果在我的 EXISTS 条件中返回单行或 1​​00,000 行,则无关紧要......如果它正在运行的行返回任何结果,那么您已经通过了存在检查。那么为什么我的子查询 SELECT 语句返回什么重要呢?

------------------编辑----------

根据请求,以下是我在 TOAD 中运行的执行计划...请注意,为方便起见,我在上面的示例中编辑了表名 - 在这些计划中 ALSS_SALES2 = 上面的销售额和 SALESEXT_TMP = 上面的 tempTABLE。

也应该提到,但此时这两个表都没有索引。我还没有将它们添加到我的 tempTable 中,我正在使用仅包含字段和数据的 sales 表的廉价副本进行测试但没有索引、约束或安全性。

感谢大家的帮助!

查询 1 执行计划

查询 2 执行计划

------------------------------------------ ----

问题

1) 为什么调用 rownum 会导致执行计划发生变化?

2) 过滤器效率如此之低的原因是什么?

3) 我是否遗漏了导致这种变化的 Exists 子句的工作方式的一些基本内容?

【问题讨论】:

  • 我的假设是,当您使用 * 时,引擎使用最相关的索引但仅返回 rownum 可能会忽略索引并执行顺序计划。但我不确定
  • 能否包括执行计划,包括访问和过滤部分?

标签: sql oracle exists query-performance rownum


【解决方案1】:

发布实际的查询计划会很有帮助。

不过,一般来说,当优化器看到带有rownum 的子查询时,这会从根本上限制其转换查询并将子查询的结果与主查询合并的能力,因为这样做可能会影响结果。如果这恰好比优化器选择的计划更有效,那么这可能是强制 Oracle 实现子查询的一种快速方法。但是,在这种情况下,它可能会导致优化器放弃使查询更有效的转换步骤。

有时,您会看到有人提出类似的查询

SELECT b.*
  FROM (SELECT <<columns>>
          FROM driving_table
         WHERE <<conditions>>) a,
       b
 WHERE a.id = b.id

并将rownum 添加到a 子查询

SELECT b.*
  FROM (SELECT <<columns>>, rownum
          FROM driving_table
         WHERE <<conditions>>) a,
       b
 WHERE a.id = b.id

为了强制优化器在执行连接之前评估a 子查询。当然,如果效率更高,优化器通常应该默认执行此操作。但是,如果优化器出错,添加 rownum 比找出正确的提示集来强制执行计划或深入研究潜在问题以找出正确的解决方案更快。

当然,在特定情况下,您在 WHERE EXISTS 中有一个子查询,其中 rownum 的唯一用途出现在 SELECT 列表中,我们人类可以检测到 rownum 不应该阻止任何优化器愿意使用的查询转换步骤。不过,优化器可能使用了更通用的规则,即引用 rownum 之类的函数的子查询必须完全执行(这可能取决于确切的 Oracle 版本和/或优化器设置)。所以优化器实际上做了一堆额外的工作,因为它不够聪明,无法识别你添加的rownum 不可能影响查询的结果。

【讨论】:

  • 感谢贾斯汀,这实际上很有意义,并为我提供了很多关于 rownum 的好信息。我必须记住将 rownum 添加到子查询以尽早实现它的技巧。谢谢!
【解决方案2】:

只是一个问题,这个查询的执行计划是什么:

UPDATE sales s
   SET status = 'DONE', trandate = sysdate
 WHERE EXISTS (Select NULL
 FROM tempTable tmp
     WHERE s.key1 = tmp.key1
       AND s.key2 = tmp.key2
       AND s.key3 = tmp.key3);

它将EXISTS (...) 表达式中需要的内容可视化 - 实际上什么都没有!如前所述,Oracle 只需要检查 if 是否返回任何内容,而不是检查子查询中返回的 what

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-05
    • 1970-01-01
    • 1970-01-01
    • 2014-01-31
    相关资源
    最近更新 更多