暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

CBO 查询转换(2):反结合的NULL识别机能(null aware anti-join )

468

编者按:

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

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

其实上一篇文章的初衷是为了捋顺一下 null aware anti-join 机能做的一个铺垫。为什么专门重点来说 Null aware(NA) 机能呢,是因为工作中可能会遇到过这个机能造成的 BUG 还是比较多,所以,想做个单独总结。

反结合的NULL识别机能(null aware anti-join )

前面的文章已经说过了子查询展开机能,这个机能在有些时候是没法使用的,比如 NOT IN 子句中坑包含 NULL 。假如,NOT IN 子句中成员中有 NULL 的话就相当于 !=ALL。

那这种情况下,CBO 如何来转换用户的 SQL 呢。11g 开始 Oracle 为我们提供了 null aware anti-join 机能,我们再来看看这个机能长什么样子。

首先还是看看在没有这个机能之前 SQL 是怎么执行的。

稍微修改一下前面文章的测试 case 中表的字段定义,将 C2 列 not null 的限制去掉。

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

    -- 无效 null aware anti-join 机能
    SQL> alter session set "_optimizer_null_aware_antijoin"=FALSE;
    SQL> select t1.* from t1 where c2 not in (select 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 | 3 | 78 | 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 0 FROM "T2" "T2" WHERE
    LNNVL("C2"<>:B1)))
    3 - filter(LNNVL("C2"<>:B1))


    SQL> select t1.* from t1 where c2 not in (select c2 from t2 where c2 is not null);
    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 | 3 | 78 | 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 0 FROM "T2" "T2" WHERE "C2" IS NOT
    NULL AND LNNVL("C2"<>:B1)))
    3 - filter("C2" IS NOT NULL AND LNNVL("C2"<>:B1))

    因为 C2 列本身是可以是 NULL ,所以,ANTI 结合没有用上,SQL 没有经过转换。

    如果使用 null aware anti-join 机能后呢,就是下面的样子了。

      -- 有效 null aware anti-join 机能
      SQL> alter session set "_optimizer_null_aware_antijoin"=true;
      SQL> select t1.* from t1 where c2 not in (select c2 from t2);


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




      Execution Plan
      ----------------------------------------------------------
      Plan hash value: 1275484728


      ---------------------------------------------------------------------------
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
      ---------------------------------------------------------------------------
      | 0 | SELECT STATEMENT | | 3 | 117 | 6 (0)| 00:00:01 |
      |* 1 | HASH JOIN ANTI NA | | 3 | 117 | 6 (0)| 00:00:01 |
      | 2 | TABLE ACCESS FULL| T1 | 3 | 78 | 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")


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


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




      Execution Plan
      ----------------------------------------------------------
      Plan hash value: 1270581391


      ---------------------------------------------------------------------------
      | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
      ---------------------------------------------------------------------------
      | 0 | SELECT STATEMENT | | 3 | 117 | 6 (0)| 00:00:01 |
      |* 1 | HASH JOIN ANTI SNA| | 3 | 117 | 6 (0)| 00:00:01 |
      | 2 | TABLE ACCESS FULL| T1 | 3 | 78 | 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")
      3 - filter("C2" IS NOT NULL)

      完美!!SQL 转换了,子查询展开机能用到了,T1 和 T2 进行了 ANTI 结合,同时,也进行了 NULL 识别 Null-Aware(NA) 或者 Single Null-Aware(SNA) 。

      当然,我们在写 SQL 的时候假如能把带有 NULL 的可能性给排除掉的话,我认为是最理想的,可以避免很多不必要的麻烦,这就要求各位程序员同学们编写 SQL 时需要注意到一些细节,不要过分指望 Oracle 的优化器来排除全部问题。不知道各位怎么认为。

        SQL> select t1.* from t1 where c2 is not null and c2 not in (select c2 from t2 where c2 is not null);
        C1 C2
        ---------- ----------
        1 1


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


        ---------------------------------------------------------------------------
        | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
        ---------------------------------------------------------------------------
        | 0 | SELECT STATEMENT | | 1 | 39 | 6 (0)| 00:00:01 |
        |* 1 | HASH JOIN ANTI | | 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")
        2 - filter("C2" IS NOT NULL)
        3 - filter("C2" IS NOT NULL)

        null aware anti-join 的关闭方法,上面的例子中已经用到,就不再赘述了。

        后续文章更加精彩,欢迎关注本公众号或访问【阅读原文】。


        ——End——

        专注于技术不限于技术!

        用碎片化的时间,一点一滴地提高数据库技术和个人能力。

        欢迎关注!

        数据库基础系列(从基础了解数据库):

        数据库性能问题分析和诊断方法概论

        一图了解Oracle数据库简史

        数据库的“黑匣子”--故障诊断日志基础

        通过寄存服务来“理解”Oracle数据库基本体系结构和动作流程

        Oracle优化器架构变化和特定行为

        手把手系列(帮助个人技术成长):

        SQL调优和诊断从哪入手?

        获取SQL执行计划最基础的方法是啥?

        一学就会的获取SQL执行计划和性能统计信息的方法

        在线Oracle SQL学习环境--Live SQL

        Oracle优化器架构变化和特定行为

        CBO 查询转换系列(了解Oracle优化器)

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

        文章转载自Oracle数据库技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论