前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CBO 查询变化(1):子查询展开机能(Subquery Unnesting)

CBO 查询变化(1):子查询展开机能(Subquery Unnesting)

作者头像
SQLplusDB
发布2022-08-19 20:27:29
2400
发布2022-08-19 20:27:29
举报
文章被收录于专栏:Oracle数据库技术

编者按:

本文作者系杨昱明,现就职于甲骨文公司,从事数据库方面的技术支持。希望能通过发表文章,把一些零散的知识再整理整理。个人主页:https://blog.csdn.net/weixin_50513167,经其本人授权发布。

【免责声明】本号文章仅代表个人观点,与任何公司无关。

有时候用户写的 SQL,对于优化器来说并不一定是最好的,可能作出的执行计划不会用到合适的结合处理。

所以,CBO 会在作执行计划之前,用一堆十分难懂的机能去转换用户作的 SQL。对于这些转换机能想做一些浅显的整理总结,也希望同时学习的小伙伴们给与斧正。

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

子查询展开机能(Subquery Unnesting)

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

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

代码语言:javascript
复制
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;
代码语言:javascript
复制
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”) 条件的数据集。

代码语言:javascript
复制
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 的情况。

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

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

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

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

代码语言:javascript
复制
隐含参数 _UNNEST_SUBQUERY 设置成 false
OR
最开始例子里面用到的 NO_UNNEST hint。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SQL和数据库技术 微信公众号,前往查看

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

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

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