如何防止 Oracle 基于成本的优化器进行不良优化?

发布于 2024-10-02 02:51:16 字数 1043 浏览 6 评论 0原文

考虑以下场景。我在一个我无法控制的模式中有一个表(一个stupid_table)。这是第三方,禁止入内。不敏感。我可以查询它,但不能添加索引或新表或更改设计。

Stupid_table 中的每一列都是一个 VARCHAR2(50 BYTE) ,有很多列,但我只需要其中两列:row_type 和 <代码>magic_number。 magic_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 功能。

有更好的方法吗?

Consider the following scenario. I have a table (a stupid_table) in a schema over which I have no control. It's third party, off limits. No touchy. I can query it, but not add indexes or new tables or change the design.

Every column in the stupid_table is a VARCHAR2(50 BYTE), there are lots of columns but I only need two of them: row_type and magic_number. The magic_number is populated with the string representation of an integer, but only where row_type is set to 'DATA', I only need the magic numbers that are greater than zero.

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

This results in an "invalid number" Oracle error, because the Cost Based Optimiser (CBO) is choosing to evaluate the TO_NUMBER before the checking the row_type and there's a whole bunch of rows with a different row_type and a different use for the magic_number field.

OK, how about if I filter the rows first, and then do the comparison?

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;

Now the CBO seems to work out that the query is quite simple and ignores the cunning that I have employed, yielding an identical query plan to the original.

Finally, in frustration, I resort to dirty hacks: using the /*+RULE*/ query hint to force Oracle to use the old Rule Based Optimiser. This works like a dream, but it shouldn't be necessary, not to mention it's using a feature of Oracle which is no longer supported.

Is there a better way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

泪之魂 2024-10-09 02:51:16

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

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 谓词更容易管理。

I would solve this by writing your own conversion function that swallows the exception, i.e.

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;

and then change the query

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

Barring that, you could certainly take the query plan generated by the RBO and create a profile that forces the CBO to use that plan. That's probably a bit easier to manage than trying to provide a complete set of hints that would prevent the CBO from ever applying the MAGIC_NUMBER predicate before the ROW_TYPE predicate.

少女的英雄梦 2024-10-09 02:51:16

让 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;

Make CASE do the work for you

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

In my test case, I had trouble recreating your error so wonder if maybe there are some DATA rows that don't have numbers in them. But it could also be the way the optimizer is treating my queries.

I would think that a no_merge hint might also solve your problem but since I had trouble reproducing the issue, I can't be sure.

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;
仄言 2024-10-09 02:51:16

您可以完全避免使用 TO_NUMBER 吗?看起来无论如何都会提高性能。比如:

WHERE t.magic_number != '0'

如果可能有负数,或者数字是浮点数,您可能需要额外的检查,但这看起来确实可行。

Can you just avoid using TO_NUMBER altogether? Seems like that would improve performance anyways. Something like:

WHERE t.magic_number != '0'

If there could be negative numbers, or the numbers are floating point, you might need additional checks, but it certainly seems feasible.

欢你一世 2024-10-09 02:51:16

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

文档:
Oracle ORDERED_PREDICATES 提示

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

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

The exact way is to use a ordered_predicates hint, to change the order your WHERE conditions are evaluated.

Documentation:
Oracle ORDERED_PREDICATES Hint

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

Now try to swap around the conditions, and you get your error again. Please also considder the other answers, because I also doubt that evoking the TO_NUMBER is your best solution.

命比纸薄 2024-10-09 02:51:16

创建仅包含“DATA”的 row_type 的愚蠢表切片的物化视图怎么样?

How about creating a materialized view of the slice of stupid_table that contains only the row_type of 'DATA'?

蓝眼泪 2024-10-09 02:51:16

我通常添加一个 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;

I usually add a rownum to stop predicate pushing. (Hints can do this too, but they are so easy to get wrong, and with this type of problem if you get it wrong you may not notice right away.) Also you should probably add a comment so someone doesn't later try to "optimize" your code and remove what looks like unnecessary logic.

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;
暗恋未遂 2024-10-09 02:51:16

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

The with statement lets you apply a specific order of evaluation.

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;

Also consider the possibility that there really is bad data in one or more 'DATA' rows.

窗影残 2024-10-09 02:51:16

您可以尝试:

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,}

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

);

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


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

)) ilv;

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

);

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

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

You might try:

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

If this still doesn't work, moving the condition into a HAVING clause might force the optimizer to evaluate it first.

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,}

Failing that, a materialized view or using a PL/SQL cursor might be the only way to go.

);

If this still doesn't work, moving the condition into a HAVING clause might force the optimizer to evaluate it first.


Failing that, a materialized view or using a PL/SQL cursor might be the only way to go.

)) ilv;

Failing that, a materialized view or using a PL/SQL cursor might be the only way to go.

);

If this still doesn't work, moving the condition into a HAVING clause might force the optimizer to evaluate it first.

Failing that, a materialized view or using a PL/SQL cursor might be the only way to go.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文