`

[ORACLE]详解not in与not exists的区别与用法(转载)

 
阅读更多
转载(http://blog.sina.com.cn/s/blog_6ff05a2c0100tref.html)
在网上搜了下关于oracle中not exists和not in性能的比较,发现没有描述的太全面的,可能是问题太简单了,达人们都不屑于解释吧。于是自己花了点时间,试图把这个问题简单描述清楚,其实归根结底一句话:not in性能并不比not exists差,关键看你用的是否正确。

我先建两个示范表,便于说明:
create table  ljn_test1 (col number);
create table  ljn_test2 (col number);

然后插入一些数据:

insert into ljn_test1
select level from dual connect by level <=30000;

insert into ljn_test2

select level+1 from dual connect by level <=30000;

commit;

然后来分别看一下使用not exists和not in的性能差异:

select * from ljn_test1 where not exists (select 1 from ljn_test2 where ljn_test1.col = ljn_test2.col);
       COL
----------
         1
Elapsed: 00:00:00.06

select * from ljn_test1 where col not in (select col from ljn_test2);
       COL
----------
         1
Elapsed: 00:00:21.28

可以看到,使用not exists需要0.06秒,而使用not in需要21秒,差了3个数量级!为什么呢?其实答案很简答,以上两个SQL其实并不是等价的。

我把以上两个表的数据清除掉,重新插入数据:

truncate table ljn_test1;
truncate table ljn_test2;
insert into ljn_test1 values(1);
insert into ljn_test1 values(2);
insert into ljn_test1 values(3);
insert into ljn_test2 values(2);
insert into ljn_test2 values(null);
commit;

然后再次执行两个SQL:

select * from ljn_test1 where not exists (select 1 from ljn_test2 where ljn_test1.col = ljn_test2.col);
       COL

----------
         3
         1
select * from ljn_test1 where col not in (select col from ljn_test2); 

no rows selected

这回not in的原形暴露了,竟然得到的是空集。来仔细分解一下原因:

A.  select * from ljn_test1 where col not in (select col from ljn_test2);

A在这个例子中可以转化为下面的B:

B.  select * from ljn_test1 where col not in (2,null);

B可以进一步转化为下面的C:

C.  select * from ljn_test1 where col <> 2 and col <> null;

因为col <> null是一个永假式,所以最终查出的结果肯定也就是空了。

由此可以得出结论:只要not in的子查询中包含空值,那么最终的结果就为空!

not exists语句不会出现这种情况,因为not exists子句中写的是ljn_test1与ljn_test2的关联,null是不参与等值关联的,所以ljn_test2的col存在空值对最终的查询结果没有任何影响。

我在这里暂且把ljn_test1叫做外表,ljn_test2叫做内表。

只要稍做归纳,就可以得到更详细的结论:

1、对于not exists查询,内表存在空值对查询结果没有影响;对于not in查询,内表存在空值将导致最终的查询结果为空。

2、对于not exists查询,外表存在空值,存在空值的那条记录最终会输出;对于not in查询,外表存在空值,存在空值的那条记录最终将被过滤,其他数据不受影响。

 

讲到这里,我就可以开始解释为什么上面的not in语句比not exists语句效率差这么多了。

not exists语句很显然就是一个简单的两表关联,内表与外表中存在空值本身就不参与关联,在CBO(基于成本的优化器)中常用的执行计划是hash join,所以它的效率完全没有问题,看一下它的执行计划:

set autot on;

select * from ljn_test1 where not exists (select 1 from ljn_test2 where ljn_test1.col = ljn_test2.col);
       COL
----------

         3
         1
Elapsed: 00:00:00.01 

Execution Plan

----------------------------------------------------------
Plan hash value: 385135874

--------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |     3 |    78 |     7  (15)| 00:00:01 |
|*  1 |  HASH JOIN ANTI    |           |     3 |    78 |     7  (15)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| LJN_TEST1 |     3 |    39 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| LJN_TEST2 |     2 |    26 |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------- 

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - access("LJN_TEST1"."COL"="LJN_TEST2"."COL") 

这个执行计划很清晰,没有什么需要解释的,再看一下not in:

select * from ljn_test1 where col not in (select col from ljn_test2); 

no rows selected 

Elapsed: 00:00:00.01
Execution Plan
----------------------------------------------------------

Plan hash value: 3267714838

--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     1 |    13 |     5   (0)| 00:00:01 |
|*  1 |  FILTER            |           |       |       |            |          |
|   2 |   TABLE ACCESS FULL| LJN_TEST1 |     3 |    39 |     3   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| LJN_TEST2 |     2 |    26 |     2   (0)| 00:00:01 |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

--------------------------------------------------- 
   1 - filter( NOT EXISTS (SELECT 0 FROM "LJN_TEST2" "LJN_TEST2"
              WHERE LNNVL("COL"<>:B1)))
   3 - filter(LNNVL("COL"<>:B1))

可以看到关联谓词是filter,它类似于两表关联中的nested loop,也就是跑两层循环,可见它的效率有多差。为什么not in不能使用hash join作为执行计划呢?正如上面解释的,因为内表或外表中存在空值对最终结果产生的影响是hash join无法实现的,因为hash join不支持把空值放到hash桶中,所以它没办法处理外表和内表中存在的空值,效率与正确性放在一起时,肯定是要选择正确性,所以oracle必须放弃效率,保证正确性,采用filter谓词。
这个执行计划中我们还有感兴趣的东西,那就是:LNNVL("COL"<>:B1),关于LNNVL的解释可以参见官方文档:http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions078.htm
它在这里的作用很巧妙,oracle知道使用filter性能很差,所以它在扫描内表ljn_test2时,会使用LNNVL来检查ljn_test2.col是否存在null值,只要扫描到null值,就可以断定最终的结果为空值,也就没有了继续执行的意义,所以oracle可以马上终止执行,在某种意义上它弥补了filter较差的性能。
我用例子来证明这一点,首先先造一些数据:

truncate table ljn_test1;
truncate table ljn_test2;
insert into ljn_test1
select level from dual connect by level <=30000;
insert into ljn_test2
select level+1 from dual connect by level <=30000;
commit;

然后我为了让oracle尽快扫描到ljn_test2.col为null的那条记录,我要先找到物理地址最小的那条记录,因为通常情况全表扫描会先扫描物理地址最小的那条记录:

select col from ljn_test2 where rowid=(select min(rowid) from ljn_test2);
       COL
----------
      1982

然后我把这条记录更新为空:

update ljn_test2 set col = null where col=1982;
commit;

然后再来看一下not in的查询效率:

select * from ljn_test1 where col not in (select col from ljn_test2);
no rows selected 

Elapsed: 00:00:00.17


看到这个结果后我很爽,它和之前查询需要用时21秒有很大的差别!

当然,我们不能总是指望oracle扫描表时总是最先找到null值,看下面的例子:

update ljn_test2 set col = 1982 where col is null;

select col from ljn_test2 where rowid=(select max(rowid) from ljn_test2);
       COL
----------
     30001

update ljn_test2 set col = null where col=30001;

commit;

再看一下not in的查询效率:

select * from ljn_test1 where col not in (select col from ljn_test2);
       COL
----------
         1
Elapsed: 00:00:21.11

这一下not in再一次原形毕露了!

机会主义不行,更杯具的是如果内表中没有空值,那LNNVL优化就永远起不到作用,相反它还会增大开销!

其实只要找到原因,问题很好解决,不就是空值在作怪嘛!在正常的逻辑下用户本来就是想得到和not exists等价的查询结果,所以只要让oracle知道我们不需要空值参与进来就可以了。

第一种解决方案:

将内表与外表的关联字段设定为非空的:

alter table ljn_test1 modify col not null;
alter table ljn_test2 modify col not null;

好了,再看一下执行计划:

set autot on;

select * from ljn_test1 where col not in (select col from ljn_test2);
       COL
----------
         1
Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 385135874 
--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     1 |    26 |    28   (8)| 00:00:01 |
|*  1 |  HASH JOIN ANTI    |           |     1 |    26 |    28   (8)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| LJN_TEST1 | 30000 |   380K|    13   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| LJN_TEST2 | 30000 |   380K|    13   (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("COL"="COL")
 

很好!这回oracle已经知道使用hash join了!不过有时候表中需要存储空值,这时候就不能在表结构上指定非空了,那也同样简单:

第二种解决方案:

查询时在内表与外表中过滤空值。

先把表结构恢复为允许空值的:

alter table ljn_test1 modify col null;

alter table ljn_test2 modify col null;

然后改造查询:
select * from ljn_test1 where col is not null and col not in (select col from ljn_test2 where col is not null);
       COL
----------
         1
Elapsed: 00:00:00.07
Execution Plan
----------------------------------------------------------
Plan hash value: 385135874

-------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |     1 |    26 |    28   (8)| 00:00:01 |
|*  1 |  HASH JOIN ANTI    |           |     1 |    26 |    28   (8)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| LJN_TEST1 | 30000 |   380K|    13   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| LJN_TEST2 | 30000 |   380K|    13   (0)| 00:00:01 |
-------------------------------------------------------------------------------- 

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - access("COL"="COL")
   2 - filter("COL" IS NOT NULL)
   3 - filter("COL" IS NOT NULL)


OK! hash join出来了!我想我关于not exists与not in之间的比较也该结束了。

 

分享到:
评论

相关推荐

    oracle数据库关于exists使用

    ### Oracle数据库中Exists与In的使用详解 #### 一、Exists 的使用方法 在Oracle数据库中,`EXISTS` 是一种常用的子查询操作符,用于判断子查询是否有结果返回。如果子查询至少返回一行数据,则 `EXISTS` 表达式的...

    oracle中not exists对外层查询的影响详解

    2. 使用`IN`或`NOT IN`:对于简单的条件,`IN`或`NOT IN`子句可能会更高效,尤其是当子查询返回少量记录时。 3. 考虑索引:为子查询涉及的列创建合适的索引,可以提高查询效率。 4. 分析和优化子查询:确保子查询...

    oracle初始化参数详解

    `always_anti_join`控制优化器如何处理NOT IN子查询,可以选择NESTED_LOOPS、MERGE或HASH方法。`always_semi_join`则影响EXISTS子查询的执行,当没有索引时,半连接可能会提高性能。 2. **aq_tm_processes**:此...

    Oracle高级sql学习与练习

    Oracle高级SQL学习与练习涵盖了数据库编程中的一系列高级主题,旨在帮助数据库开发者和管理员提高解决复杂问题的能力。在Oracle数据库系统中,高级SQL技能是进行高效数据管理、查询优化和复杂数据处理的基础。 1. ...

    Oracle通配符,运算符的使用

    本文旨在深入探讨Oracle数据库中通配符与运算符的使用方法,帮助读者更好地理解和掌握这些关键概念。 #### 二、Oracle通配符详解 在Oracle数据库中,通配符主要用于模糊查询,即通过模式匹配来检索符合特定条件的...

    Oracle——sql语句优化

    ### Oracle SQL语句优化策略详解 #### 操作符优化概览 在Oracle数据库环境中,SQL语句的优化是提升查询效率、减少系统资源消耗的关键环节。通过对特定操作符的合理运用,可以显著改善数据检索的速度与准确性。本文...

    oracle查询语句优化

    - **推荐方案**:改用NOT EXISTS或外连接加判断为空的方式替换NOT IN。 ##### 3. LIKE操作符 - **使用技巧**:LIKE操作符支持通配符查询,但使用不当会影响性能。例如,“%5400%”不会使用索引;而“X5400%”会...

    oracle数据库开发面试必备

    15. **EXISTS, IN, NOT EXISTS, NOT IN的比较**:在进行数据检索时,正确使用EXISTS与NOT EXISTS通常比使用IN与NOT IN更高效,尤其是在处理大量数据时。例如: ```sql SELECT * FROM EMP WHERE EMPNO &gt; 0 AND ...

    Oracle SQL优化.pdf

    NOT IN与EXISTS** 使用`NOT IN`时,可能会导致全表扫描,尤其是在比较大的数据集上。相比之下,使用`NOT EXISTS`可以更早终止查询,提高效率。 #### 二、SQL语句性能优化策略 **1. 选用合适的ORACLE优化器** ...

    oracle优化方法

    ### Oracle优化方法详解 在IT行业中,Oracle数据库作为企业级数据管理的重要工具之一,其性能优化一直是DBA和开发人员关注的重点。本文将基于提供的文件信息,深入探讨Oracle数据库优化的关键知识点,帮助大家提高...

    Oracle中常用的通配符

    本文详细介绍了Oracle数据库中常用的通配符以及其他相关符号的含义及使用方法。通过这些符号的应用,可以使SQL查询更加灵活多样,有助于提高数据检索的准确性和效率。在实际工作中,掌握并熟练应用这些符号对于高效...

    ORACLE高效SQL书写规范

    使用`NOT`操作符可能会导致索引失效,尤其是当它与`IN`或`LIKE`一起使用时。 **2-4、WHERE子句中尽量不用IS NULL, IS NOT NULL** 尽管这些操作符通常可以正常工作,但在某些情况下它们可能导致索引失效,特别是在...

    oracle触发器详解

    本文将深入解析Oracle触发器的原理、类型、创建及使用方法。 首先,触发器主要分为三种类型:行级触发器、语句级触发器和定时触发器。行级触发器在每一行数据受到影响时触发,而语句级触发器则在整个SQL语句执行...

    ORACLE批量更新四种方法.txt ORACLE批量更新四种方法.txt

    ### Oracle 批量更新四种方法详解 #### 一、背景介绍 在数据库管理与应用开发过程中,经常需要对大量数据进行更新操作。特别是在Oracle数据库环境下,如何高效地执行批量更新,成为了提高系统性能的关键因素之一。...

    Oracle SQL优化

    - **解决方案**: 使用EXISTS或NOT EXISTS代替IN来提高查询性能。 #### 三、SQL语句性能优化 ##### 3.1 选用合适的Oracle优化器 - **选择**: Oracle提供了两种优化器——基于规则的优化器(RBO)和基于成本的优化器...

    ORACLE 面试题集锦

    - 子查询:理解并运用嵌套查询,如IN、NOT IN、EXISTS、ANY、ALL等。 - 分区查询:了解Oracle的分区表概念,如范围分区、列表分区、哈希分区等。 - 分页查询:掌握ROWNUM和ROW_NUMBER()函数进行分页查询。 3. **...

    Oracle OCP认证课程大纲

    - **NOT IN 与 NOT EXISTS 的区别**:NOT IN 用于排除指定集合中的值,而 NOT EXISTS 更关注于子查询是否返回任何行。 - **聚合函数**:如 COUNT、SUM、AVG 等,以及它们在处理分组数据时的应用。 - **聚合函数与...

    Oracle SQL最佳实践

    - **其他常见错误**:避免使用NOT EXISTS子句,优先使用LEFT JOIN + IS NULL;合理利用分区技术等。 综上所述,Oracle SQL的最佳实践不仅涉及到具体的SQL语句优化技巧,还需要从整体架构、数据库设计、系统配置等多...

Global site tag (gtag.js) - Google Analytics