Oracle优化器对谓词顺序处理的一个场景

Posted bisal(Chen Liu)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle优化器对谓词顺序处理的一个场景相关的知识,希望对你有一定的参考价值。

最近听了个讲座,其中介绍到了Oracle的谓词,原始版本的例子,如下所示,从数据上能看到,c1='3'的时候,c2的值是个字符串类型的数字,

SQL> create table test(c1 char(1), c2 varchar2(1));
Table created.


SQL> insert into test values('1', 'A');
1 row created.


SQL> insert into test values('2', 'A');
1 row created.


SQL> insert into test values('3', '3');
1 row created.

如下测试,在Oracle 11g执行,提示错误,认为to_number的变量存在非法的数值类型,

SQL> select to_number(c2) as value, c1 from test where c1='3' and to_number(c2)>2;
select to_number(c2) as value, c1 from test where c1='3' and to_number(c2)>2
                                                              *
ERROR at line 1:
ORA-01722: invalid number

之所以提示这个,我们能从执行计划中找到原因,可以看到,谓词条件先执行的是to_number(c2),

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |     3 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST  |     1 |     5 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter((TO_NUMBER("C2")>2 AND "C1"='3'))

因为我们知道,c1='1'、c1='2'的时候,c2是'A',to_number('A')就会提示invalid number,

SQL> select to_number('A') from dual;
select to_number('A') from dual
                 *
ERROR at line 1:
ORA-01722: invalid number

从这儿我们还可以知道一点,就是Oracle的where各条件的位置其实并不重要,准确来说,在CBO优化器模式下,究竟是c1='3' and to_number(c2)>2,还是to_number(c2)>2 and c1='3',都会经过Oracle的查询改写,给出真正执行的条件顺序,可以从10053找到他的查询改写。如上例子中,按照语义,就是先执行c1='3',按说他得到的c2就是数值类型的字符串,可以使用to_number函数,但实际上无论where中怎么写,都是按照to_number(c2)>2 and c1='3'来执行,因此报错。但是如果使用RBO优化器,where条件的顺序就可能影响执行计划。

说明了Oracle真正执行的语句是经过他的优化器调整的“他认为”的路径,但这个路径很可能导致语句执行错误,如上的示例中,如果先执行c1='3',再执行to_number(c2),应该就可以。

如果按照语义,改造一下,子查询中指定where c1='3',打算得到的c2都是数值的字符串类型,然后在外层where value(to_number(c2))>2,但是仍然报"ORA-01722: invalid number",

SQL> select * from (select to_number(c2) as value, c1 from test where c1='3') where value>2;

从执行计划,能看到谓词条件仍是如下这个,虽然to_number(c2)在外层,但实际执行时,Oracle通过谓词推入,将外层条件和内层条件进行了整合,

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |     3 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST  |     1 |     5 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter((TO_NUMBER("C2")>2 AND "C1"='3'))

针对这个问题,可以改造如下,子查询中增加rownum>=1,避免谓词推入,

SQL> select * from (select to_number(c2) as value, c1 from test where c1='3' and rownum>=1) where value>2;

执行计划的谓词条件就改成了如下,并未做谓词推入,而是按照我们的“语义”选择的执行路径,这样就可以先找到c1='3'的记录,他的c2是'3',就可以正常用to_number()函数了,

-------------------------------------------------------------------------------
| Id  | Operation             | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |       |       |       |     3 (100)|          |
|*  1 |  VIEW                 |       |     1 |       |     3   (0)| 00:00:01 |
|*  2 |   COUNT               |       |       |       |            |          |
|*  3 |    FILTER             |       |       |       |            |          |
|*  4 |     TABLE ACCESS FULL | TEST  |     1 |     5 |            | 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("VALUE">2)
   3 - filter(ROWNUM>=1)
   4 - filter("C1"='3')

另外一种方式,就是采用ordered_predicates这个HINT,CBO下强制按照where条件书写顺序来执行,

select /*+ ordered_predicates */ to_number(c2) as value, c1 from test where c1='3' and to_number(c2)>2;
TO_NUMBER(C2) C
------------- -
            3 3

可以看到,谓词条件就改为,达到了我们需要让条件c1='3'先执行的目的,

1 - filter(("C1"='3' AND TO_NUMBER("C2")>2))

可能有细心的朋友发现,上述例子中c2的类型是char,如果是number或者varchar2,现象还相同么?

眼见为实,如果c2定义为number或者varchar2(1),

无论何种写法,都可以正常执行,

从谓词条件,能知道他的顺序就是我们需要的,先按照c1='3'进行过滤,他得到的c2,都是能使用to_number()函数的值,因此不会报错,

1 - filter(("C1"='3' AND TO_NUMBER("C2")>2))

说明Oracle在做查询转换的时候,针对char、varchar2、number不同的数据类型,可能设置了不同的优先级,才导致了不同的执行条件顺序。

从这个案例中,还可以提醒我们一点,就是字段的含义和类型应该保持一致,这是在数据库设计阶段需要注意的。例如c1存储的就是“数字”,如果定义为字符串类型,varchar2还好,char就出现了上述执行错误的场景,如果“数字”就使用数值类型number存储,就会绕过这个坑,同理,像“日期”用字符串类型存储存在相同的问题,可以参考《为什么日期不建议使用VARCHAR2或者NUMBER?》。

因此,有时一些表象问题背后,可能蕴藏着非规范的问题,或者说其实可以通过规范的设计和开发,避免这些“坑”,知其然更要知其所以然,更要从根本,解决碰到的问题。

近期更新的文章:

究竟哪些语句是属于DDL?

最近碰到的问题

关于数据治理的读书笔记 - 数据治理能力成熟度评估

隐式转换的替代方案不是万能的

SWIFT国际清算体系的科普贴

文章分类和索引:

公众号900篇文章分类和索引

以上是关于Oracle优化器对谓词顺序处理的一个场景的主要内容,如果未能解决你的问题,请参考以下文章

SQL 优化技术系列: 谓词下推

WHERE 谓词的顺序和 SQL 优化器

MySQL执行计划都有那些规划?

Oracle统计信息不准(谓词越界)造成的性能问题

hive sql 优化

MySQL执行计划都有那些规划?