【问题标题】:How do you prevent Oracle's Cost Based Optimiser making bad optimisations?您如何防止 Oracle 的基于成本的优化器进行糟糕的优化?
【发布时间】:2011-05-08 14:52:48
【问题描述】:

考虑以下场景。我在我无法控制的架构中有一个表(stupid_table)。它是第三方,不受限制。没有敏感。我可以查询它,但不能添加索引或新表或更改设计。

stupid_table 中的每一列都是VARCHAR2(50 BYTE),有很多列但我只需要其中两个:row_typemagic_numbermagic_number 填充了整数的字符串表示形式,但row_type 设置为'DATA',我只需要大于零的幻数。

SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;

这会导致“无效数字”Oracle 错误,因为基于成本的优化器 (CBO) 选择在检查 row_type 之前评估 TO_NUMBER,并且有一大堆行具有不同的 row_type以及 magic_number 字段的不同用途。

好的,如果我先过滤行,然后进行比较呢?

SELECT TO_NUMBER(t.magic_number)
FROM (
    SELECT magic_number
    FROM stupid_table
    WHERE row_type = 'DATA'
) t
AND TO_NUMBER(t.magic_number) > 0;

现在 CBO 似乎发现查询非常简单,忽略了我使用的狡猾,产生了与原始查询计划相同的查询计划。

最后,无奈之下,我求助于肮脏的技巧:使用/*+RULE*/ 查询提示来强制Oracle 使用旧的基于规则的优化器。这就像一个梦想,但它不应该是必要的,更不用说它正在使用不再支持的 Oracle 功能。

有没有更好的方法来做到这一点?

【问题讨论】:

    标签: sql oracle ora-01722


    【解决方案1】:

    确切的方法是使用ordered_predicates 提示来更改评估WHERE 条件的顺序。

    文档: Oracle ORDERED_PREDICATES Hint

    SELECT /*+ ORDERED_PREDICATES */ TO_NUMBER(magic_number)
    FROM stupid_table
    WHERE row_type = 'DATA'
    AND TO_NUMBER(magic_number) > 0;
    

    现在尝试交换条件,你又得到了你的错误。还请考虑其他答案,因为我也怀疑调用 TO_NUMBER 是您的最佳解决方案。

    【讨论】:

    • 警告,ORDERED_PREDICATES 在 _optimizer_ignore_hints 为真时被忽略
    【解决方案2】:

    我通常添加一个 rownum 来停止谓词推送。 (提示也可以做到这一点,但它们很容易出错,如果你出错了,你可能不会马上注意到这种类型的问题。)另外,你可能应该添加评论,这样以后就不会有人尝试“优化”您的代码并删除看似不必要的逻辑。

    SELECT TO_NUMBER(t.magic_number)
    FROM (
        --Bad data, use rownum for type safety
        SELECT magic_number, rownum
        FROM stupid_table
        WHERE row_type = 'DATA'
    ) t
    AND TO_NUMBER(t.magic_number) > 0;
    

    【讨论】:

    • 你测试过这个吗?我怀疑优化器会完全丢弃rownum,因为它从未在子查询之外被引用。
    • 是的,我多年来一直在使用这种方法,而且它总是有效的。似乎不应该,但 ROWNUM 的处理方式不同:download.oracle.com/docs/cd/E11882_01/server.112/e17118/… 我认为这是我使用它的部分原因;它看起来很奇怪,让你停下来思考发生了什么。
    • 添加 rownum 对于我来说一些难以调优的查询非常有用。
    【解决方案3】:

    with 语句允许您应用特定的求值顺序。

    WITH
    has_numerics_only AS
    (
        SELECT magic_number
        FROM stupid_table
        WHERE row_type = 'DATA'
    )
    SELECT TO_NUMBER(t.magic_number)
    FROM has_numerics_only
    WHERE TO_NUMBER(t.magic_number) > 0;
    

    还要考虑在一个或多个“DATA”行中确实存在错误数据的可能性。

    【讨论】:

    • +1 但是:这适用于当前版本的数据库,但我认为不能保证 Oracle 将来永远不会为 CTE 实现合并路径。
    【解决方案4】:

    让 CASE 为您完成工作

    select to_number(magic_number) 
    from stupid_table
    where row_type = 'DATA'
    and case when row_type = 'DATA' then to_number(magic_number) else 0 end > 0
    

    在我的测试用例中,我无法重新创建您的错误,所以想知道是否有一些 DATA 行中没有数字。但这也可能是优化器处理我的查询的方式。

    我认为 no_merge 提示也可能解决您的问题,但由于我无法重现该问题,我无法确定。

    SELECT --+ no_merge(t)
      TO_NUMBER(t.magic_number)
    FROM (
        SELECT magic_number
        FROM mike_temp_stupid_table
        WHERE row_type = 'DATA'
    ) t
    where TO_NUMBER(t.magic_number) > 0;
    

    【讨论】:

    • 我只是在输入基本相同的信息。但在我的测试中,NO_MERGE 提示似乎没有什么不同。它禁用“复杂视图合并”,但优化器在这里所​​做的是“过滤器下推”。似乎没有禁用此功能的提示; NO_PUSH_PRED 影响“连接谓词下推”。
    【解决方案5】:

    如何创建一个只包含“DATA”行类型的愚蠢表切片的物化视图?

    【讨论】:

      【解决方案6】:

      你可以试试:

      SELECT TO_NUMBER(magic_number)
      FROM stupid_table
      WHERE row_type = 'DATA'
      AND REGEXP_LIKE(magic_number, '^\d{1,}$');
      

      如果这仍然不起作用,将条件移动到 HAVING 子句中可能会强制优化器首先对其进行评估。

      SELECT TO_NUMBER(magic_number)
      FROM (
      SELECT magic_number
      FROM stupid_table
      WHERE row_type = 'DATA'
      GROUP BY magic_number
      HAVING REGEXP_LIKE(magic_number, '^\d{1,}$')) ilv;
      

      如果做不到这一点,物化视图或使用 PL/SQL 游标可能是唯一的方法。

      【讨论】:

      • 我偶然发现了一个类似的问题,在你回答的前半部分,你仍然依赖于 REGEXP_LIKE 的评估顺序。例如,如果您有另一个谓词可能是 magic_number > 10magic_number < 100,那么您的查询可能会因“无效号码”而失败
      【解决方案7】:

      我会通过编写你自己的转换函数来解决这个问题,即吞下异常,即

      CREATE OR REPLACE FUNCTION my_to_number( p_str IN VARCHAR2 )
        RETURN number
      IS 
      BEGIN
        RETURN to_number( p_str );
      EXCEPTION
        WHEN OTHERS THEN
          RETURN null;
      END;
      

      然后更改查询

      SELECT TO_NUMBER(magic_number)
      FROM stupid_table
      WHERE row_type = 'DATA'
      AND MY_TO_NUMBER(magic_number) > 0;
      

      除此之外,您当然可以采用 RBO 生成的查询计划并创建强制 CBO 使用该计划的配置文件。这可能比尝试提供一套完整的提示来防止 CBO 在 ROW_TYPE 谓词之前应用 MAGIC_NUMBER 谓词更容易管理。

      【讨论】:

      • +1 用于编写转换函数,我喜欢这个想法并且它确实有效。
      • 是的 - 这是我非常喜欢的解决方案。我不确定其他人如何尝试解决类似情况,但对于我的一般用途,我最终得到了几个“受保护”版本的操作:safe_to_number、safe_div(在被零除的情况下返回 null)等。
      【解决方案8】:

      你能完全避免使用TO_NUMBER 吗?似乎无论如何都会提高性能。比如:

      WHERE t.magic_number != '0'
      

      如果可能有负数,或者数字是浮点数,您可能需要进行额外检查,但这似乎确实可行。

      【讨论】:

      • 有时最简单的解决方案往往是最好的。虽然这可能不是此类问题的最佳通用解决方案,但它确实以最高效的方式解决了我的特定问题。
      猜你喜欢
      • 2015-11-23
      • 1970-01-01
      • 2017-02-28
      • 1970-01-01
      • 1970-01-01
      • 2012-05-04
      • 2017-12-10
      • 2012-04-29
      • 1970-01-01
      相关资源
      最近更新 更多