2010-11-11 33 views
3

考虑以下情况。我有一个表格(一个stupid_table)在我无法控制的架构中。这是第三方,不受限制。没有敏感。我可以查询它,但不添加索引或新表或更改设计。如何防止Oracle的基于成本的优化器进行糟糕的优化?

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_typemagic_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特性。

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

回答

3

你能完全避免使用TO_NUMBER吗?似乎这样可以提高性能。例如:

WHERE t.magic_number != '0' 

如果可能有负数,或数字是浮点数,则可能需要额外的检查,但它看起来似乎是可行的。

+1

有时最简单的解决方案往往是最好的。虽然这可能不是解决这类问题的最佳通用解决方案,但它能以最高性能的方式解决*我的特殊问题。 – ninesided 2010-11-12 10:46:29

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

+1用于编写转换函数,我喜欢这个想法,它*可以*工作。 – ninesided 2010-11-12 10:47:20

+0

是的 - 这是我真正喜欢的解决方案。我不知道其他人如何解决类似的情况,但是对于我的通用用途,我最终得到了几个'受保护'版本的操作:safe_to_number,safe_div(在除零的情况下返回null)等。 – YoYo 2013-11-16 08:56:00

2

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

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

+0

我已经偶然遇到了一个类似问题,在答案的前半部分,您仍然依赖于REGEXP_LIKE的评估顺序。例如,如果您有另一个谓词,可能是'magic_number> 10'和'magic_number <100',那么您的查询可能会失败,并显示“无效号码” – 0909EM 2017-01-20 14:18:22

4

品牌凯斯为你做

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; 
+1

我只是输入基本相同的信息。但在我的测试中,NO_MERGE暗示似乎没有什么区别。它禁用“复杂的视图合并”,但优化器在这里做的是“过滤器下推”。似乎没有暗示要禁用此功能; NO_PUSH_PRED影响“连接谓词下推”。 – 2010-11-11 18:42:34

+0

+1好的答案,case case很好地工作 – ninesided 2010-11-12 10:48:01

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

+0

+1但是:这在当前版本的数据库中有效,但我不认为它可以保证Oracle将来不会实现CTE的合并路径。 – 2010-11-12 02:33:27

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; 
+0

你测试过了吗?我怀疑优化器会抛弃'rownum',因为它永远不会在子查询之外被引用。 – 2010-11-12 02:34:33

+0

是的,我一直在使用这种方法多年,它始终工作。它似乎不应该,但ROWNUM的处理方式不同:http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/queries008.htm#i2054139我认为这是我使用它;它看起来很奇怪,它会让你停下来思考发生了什么。 – 2010-11-12 04:15:14

+0

添加rownum对于一些难以调整的查询非常有用。 – user584583 2014-01-21 16:42:48

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是你最好的解决方案。

+1

在_optimizer_ignore_hints为true的情况下会忽略'ORDERED_PREDICATES'的警告字 – 0909EM 2017-01-20 14:20:57