您如何防止Oracle的基于成本的优化器进行错误的优化?

时间:2010-11-11 17:24:34

标签: sql oracle ora-01722

请考虑以下情形。我在模式中有一个表(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)选择在检查TO_NUMBER之前评估row_type并且有一大堆行与不同的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功能。

有更好的方法吗?

8 个答案:

答案 0 :(得分:4)

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

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 :(得分: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;

答案 2 :(得分:3)

你能完全避免使用TO_NUMBER吗?似乎这样可以提高性能。类似的东西:

WHERE t.magic_number != '0'

如果可能存在负数,或者数字是浮点数,则可能需要额外检查,但这似乎是可行的。

答案 3 :(得分:3)

确切的方法是使用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是你最好的解决方案。

答案 4 :(得分:2)

如何创建仅包含'DATA'的row_type的stupid_table切片的物化视图?

答案 5 :(得分: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;

答案 6 :(得分:1)

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”行中确实存在错误数据的可能性。

答案 7 :(得分:0)

您可以尝试:

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游标可能是唯一的方法。