ORACLE CBO 的 SQL 自动转换(Cost Based Transformations)之一

ORACLE CBO 的 SQL 自动转换(Cost Based Transformations)之一

有时候用户写的 SQL,对于优化器来说并不一定是最好的,可能作出的执行计划不会用到合适的结合处理。
所以,CBO 会在作执行计划之前,用一堆十分难懂的机能去转换用户作的 SQL。对于这些转换机能想做一些浅显的整理总结,也希望同时学习的小伙伴们给与斧正。

首先来说说相对简单一点子查询展开机能(Subquery Unnesting)。

子查询展开机能(Subquery Unnesting)

通常情况下,SQL 的特点是用到了 IN,NOT IN, EXISTS, NOT EXISTS 子句。

举个例子来说明,假如不使用子查询展开机能的话,执行计划就会像下面处理一样,先会对子查询进行 filter,之后再用 filter 结果对出查询进行 filter,取出数据集。这个处理过程和用户的逻辑一致吧。

drop table t1 purge;
drop table t2 purge;
create table t1(c1 number, c2 number not null);
create table t2(c1 number primary key, c2 number not null);
insert into t1 values (1,1);
insert into t1 values (1,2);
insert into t2 values (1,2);
commit;

SQL> select t1.* from t1 where c2 in (select /*+ NO_UNNEST */ c2 from t2);

        C1         C2
---------- ----------
         1          2


Execution Plan
----------------------------------------------------------
Plan hash value: 895956251

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     2 |    52 |     6   (0)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| T1   |     2 |    52 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "T2" "T2" WHERE
              "C2"=:B1))
   3 - filter("C2"=:B1)


SQL> select t1.* from t1 where c2 not in (select /*+ NO_UNNEST */ c2 from t2);

        C1         C2
---------- ----------
         1          1


Execution Plan
----------------------------------------------------------
Plan hash value: 895956251

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    26 |     6   (0)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| T1   |     2 |    52 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( NOT EXISTS (SELECT /*+ NO_UNNEST */ 0 FROM "T2" "T2"
              WHERE "C2"=:B1))
   3 - filter("C2"=:B1)

这种情况下,显然没有直接用 JOIN 来 access 更有效。
所以,子查询展开机能隆重登场,下面我们来看一下,利用子查询展开机能后,上面处理会变成什么样。

IN 子句里面的表直接同主查询的表进行了 SEMI 结合,SEMI 结合可以理解为满足 access(“C2”=“C2”) 条件的数据集。

SQL> select t1.* from t1 where c2 in (select c2 from t2);

        C1         C2
---------- ----------
         1          2


Execution Plan
----------------------------------------------------------
Plan hash value: 1713220790

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    39 |     6   (0)| 00:00:01 |
|*  1 |  ***HASH JOIN SEMI***    |      |     1 |    39 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     2 |    52 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("C2"="C2")

NOT IN 子句里面的表直接同主查询的表进行了 ANTI 结合,ANTI 结合可以理解为不满足 access(“C2”=“C2”) 条件的数据集。
但是,这里埋了一个不小的雷,就是这里没有涉及到 NULL (C2 列是 NOT NULL),之后我们再聊 NULL 的情况。

SQL> select t1.* from t1 where c2 not in (select c2 from t2);

        C1         C2
---------- ----------
         1          1


Execution Plan
----------------------------------------------------------
Plan hash value: 2706079091

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     2 |    78 |     6   (0)| 00:00:01 |
|*  1 |  ***HASH JOIN ANTI***    |      |     2 |    78 |     6   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |     2 |    52 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("C2"="C2")

上面2个例子,假如取一下 10053 trace 的话,会发现用户的 SQL 都会转换成下面这个 SQL,T1 和 T2 进行 JOIN。作出来的执行计划里,IN 的时候是 SEMI JOIN ,NOT IN 的时候是 ANTI JOIN。

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT "T1"."C1" "C1","T1"."C2" "C2" FROM "U1"."T2" "T2","U1"."T1" "T1" WHERE "T1"."C2"="T2"."C2"

大家感受到子查询展开机能的效果了吗-

那如何关闭子查询展开机能呢?有以下两种方法:

隐含参数 _UNNEST_SUBQUERY 设置成 false

OR

最开始例子里面用到的 NO_UNNEST hint。

上一篇:【Mysql】InnoDB 中的聚簇索引、二级索引、联合索引


下一篇:java 获取两时间相差月份