Oracle 优化——奇怪的执行计划左加入一个不相关的子查询

Posted

技术标签:

【中文标题】Oracle 优化——奇怪的执行计划左加入一个不相关的子查询【英文标题】:Oracle optimization -- weird execution plan to left join an uncorrelated subquery 【发布时间】:2013-07-29 20:31:47 【问题描述】:

我编写此查询是为了将产品预测数据与 Oracle 星型模式数据库中的历史发货数据联系起来,但优化器的行为与我预期的不同,所以我有点好奇发生了什么。

基本上,我有一堆维度表,它们对于预测和销售事实表都是一致的,但是事实表在不同的级别聚合,所以我将它们设置为两个子查询并将它们汇总起来,所以我可以将它们联系在一起(下面的查询示例。)在这种情况下,我想要所有的预测数据,但只想要匹配的销售数据。

奇怪的是,如果我自己使用任何一个子查询,它们的行为似乎都符合我的预期,并且每个都在不到一秒的时间内返回(使用相同的过滤器——我通过删除一个或另一个子查询并更改别名)。

这里是查询结构的一个例子——我尽可能地保持它的通用性,所以更改它可能会出现一些拼写错误:

SELECT
     TIME_DIMENSION.GREGORIAN_DATE,
     LOCATION_DIMENSION.LOCATION_CODE,
     DESTINATION_DIMENSION.REGION,
     PRODUCT_DIMENSION.PRODUCT_CODE,
     SUM(NVL(FIRST_SUBQUERY.VALUE,0)) VALUE1,
     SUM(NVL(SECOND_SUBQUERY.VALUE,0)) VALUE2
FROM
     TIME_DIMENSION,
     LOCATION_DMENSION SOURCE_DIMENSION,
     LOCATION_DIMENSION DESTINATION_DIMENSION,
     PRODUCT_DIMENSION,
     (SELECT
        FORECAST_FACT.TIME_KEY,
        FORECAST_FACT.SOURCE_KEY,
        FORECAST_FACT.DESTINATION_KEY,
        FORECAST_FACT.PRODUCT_KEY,
        SUM(FORECAST_FACT.VALUE) AS VALUE,
     FROM FORECAST_FACT
     WHERE [FORECAST_FACT FILTERS HERE]
     GROUP BY
        FORECAST_FACT.TIME_KEY,
        FORECAST_FACT.SOURCE_KEY,
        FORECAST_FACT.DESTINATION_KEY) FIRST_SUBQUERY
LEFT JOIN
    (SELECT
        --This is just as an example offset
        (LAST_YEAR_FACT.TIME_KEY + 52) TIME_KEY,        
        LAST_YEAR_FACT.SOURCE_KEY,
        LAST_YEAR_FACT.DESTINATION_KEY,
        FORECAST_FACT.PRODUCT_KEY,
        SUM(LAST_YEAR_FACT.VALUE) AS VALUE,
     FROM LAST_YEAR_FACT
     WHERE [LAST_YEAR_FACT FILTERS HERE]
     GROUP BY
        LAST_YEAR_FACT.TIME_KEY,
        LAST_YEAR_FACT.SOURCE_KEY,
        LAST_YEAR_FACT.DESTINATION_KEY) SECOND_SUBQUERY
ON
    FORECAST_FACT.TIME_KEY = LAST_YEAR_FACT.TIME_KEY
    AND FORECAST_FACT.SOURCE_KEY = LAST_YEAR_FACT.SOURCE_KEY
    AND FORECAST_FACT.DESTINATION_KEY = LAST_YEAR_FACT.DESTINATION_KEY
    --I also tried to tie the last_year subquery to the dimension tables here
WHERE
    FORECAST_FACT.TIME_KEY = TIME_DIMENSION.TIME_KEY
    AND FORECAST_FACT.SOURCE_KEY = SOURCE_DIMENSION.LOCATION_KEY
    AND FORECAST_FACT.DESTINATION_KEY = DESTINATION_DIMENSION.LOCATION_KEY
    AND FORECAST_FACT.PRODUCT_KEY  = PRODUCT_DIMENSION.PRODUCT_KEY
    --I also tried, separately, to tie the last_year subquery to the dimension tables here
    AND TIME_DIMENSION.WEEK = 'VALUE'
    AND SOURCE_DIMENSION.SOURCE_CODE = 'VALUE'
    AND DESTINATION_DIMENSION.REGION IN ('VALUE', 'VALUE')
    AND PRODUCT_DIMENSION.CLASS_CODE = 'VALUE'
GROUP BY
    TIME_DIMENSION.GREGORIAN_DATE,
    SOURCE_DIMENSION.LOCATION_CODE,
    DESTINATION_DIMENSION.REGION,
    PRODUCT_DIMENSION.PRODUCT_CODE

本质上,当我独立运行任一子查询时,它将利用索引并仅搜索特定分区的特定范围,而使用左连接时,它总是对其中一个事实表进行全表扫描。似乎正在发生的事情是 Oracle 正在将维度表过滤器应用于 第一个子查询——因此要进行左连接,它首先需要扫描整个销售表——即使我明确地绑定并过滤两次值,而不是依赖隐式过滤......我试过了。我在想这个错误吗?对我来说,优化器应该使用两个事实表上的索引,通过 WHERE 子句中的值过滤每个事实表,然后左连接结果子集。

我意识到我可以简单地将过滤器添加到每个子查询中,或者将其设置为两个独立查询的联合,但我很好奇优化引擎到底发生了什么——我如果有帮助,可以发布执行计划。

谢谢!

【问题讨论】:

可能是因为您正在混合使用 ANSI 连接和“旧式”连接。 仔细阅读:***.com/questions/11179991/…。您的连接结构产生的潜在混乱必须首先解决,否则我们可能不得不在您决定标准化的那一天重新考虑所有事情。 嗯,这很有趣!实际上,我最初使用旧样式连接的整个过程,并为这个示例切换了它,因为我认为它会更清楚。不过,老实说,我从未考虑过混合连接样式的含义,而且我在 Oracle 中从未遇到过任何问题(我看到很多查询只使用旧样式进行内部连接。这更容易,但这是一个坏习惯。)也就是说,我已经尝试过新式和旧式连接(和混合),这似乎对执行计划没有任何影响 我看不出为什么这些子查询甚至需要存在的充分理由。我确信优化器足够聪明,可以将其放回主查询。如果它感到困惑,请将其放在主查询中。我是 ANSI 连接的粉丝,它会清理 WHERE 子句,以便只显示过滤器。 把什么放回主查询?子查询的存在是因为需要聚合数据。我在这个例子中稍微简化了一点,但基本上目标事实表实际上是一个客户表,我必须在子查询中引用它,以便我可以将它聚合到一个链级别(即将所有 wal-marts 组合在一起,而不是特定的沃尔玛)在左连接发生之前。你不能加入它然后聚合它,因为你会丢失没有预测特定客户键的历史数据。 【参考方案1】:

确保所有表格都经过分析。再来一遍。优化器使用这些值来计算它的执行计划。如果 Oracle 确实选择了错误的计划,您的解决方法是使用提示 /*+ ... */ 强制优化器,指定索引的使用、连接顺序等。

【讨论】:

好的——假设所有的表都被分析过了,这看起来像是优化器选择了错误的执行计划的情况吗?如果我重写它或添加提示,我肯定可以让查询正常工作,但我想确保我不会从根本上误解优化器在这种情况下应该如何工作。

以上是关于Oracle 优化——奇怪的执行计划左加入一个不相关的子查询的主要内容,如果未能解决你的问题,请参考以下文章

ORACLE实际执行计划与预估执行计划不一致性能优化案例

ORACLE实际执行计划与预估执行计划不一致性能优化案例

oracle查看执行计划入门

Oracle性能优化之执行计划管理_超越OCP精通Oracle视频教程培训31

分析oracle的执行计划(explain plan)并对对sql进行优化实践

两个左连接SQL执行计划解析(Oracle和PGSQL对比):