前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Oracle优化器对谓词顺序处理的一个场景

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

作者头像
bisal
发布2022-03-22 07:51:28
5660
发布2022-03-22 07:51:28
举报
文章被收录于专栏:bisal的个人杂货铺

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

代码语言:javascript
复制
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的变量存在非法的数值类型,

代码语言:javascript
复制
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),

代码语言:javascript
复制
---------------------------------------------------------------------------
| 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,

代码语言:javascript
复制
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",

代码语言:javascript
复制
SQL> select * from (select to_number(c2) as value, c1 from test where c1='3') where value>2;

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

代码语言:javascript
复制
---------------------------------------------------------------------------
| 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,避免谓词推入,

代码语言:javascript
复制
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()函数了,

代码语言:javascript
复制
-------------------------------------------------------------------------------
| 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条件书写顺序来执行,

代码语言:javascript
复制
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'先执行的目的,

代码语言:javascript
复制
1 - filter(("C1"='3' AND TO_NUMBER("C2")>2))

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

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

4825b31cb6af1ec7090d95b7598d48bc.png
4825b31cb6af1ec7090d95b7598d48bc.png

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

901d7a4aa578a04ae975f3beb63a4b44.png
901d7a4aa578a04ae975f3beb63a4b44.png

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

代码语言:javascript
复制
1 - filter(("C1"='3' AND TO_NUMBER("C2")>2))

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022/03/15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档