在网上搜了下关于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]详解not in与not exists的区别与用法的更多相关文章

  1. 【转载】详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别

    实例:1,http://localhost/aaa/ (打开aaa中的index.php)结果:$_SERVER['QUERY_STRING'] = "";$_SERVER['RE ...

  2. [转载]Pytorch详解NLLLoss和CrossEntropyLoss

    [转载]Pytorch详解NLLLoss和CrossEntropyLoss 来源:https://blog.csdn.net/qq_22210253/article/details/85229988 ...

  3. HTTP详解(3)-http1.0 和http1.1 区别

    HTTP详解(3)-http1.0 和http1.1 区别 分类: 网络知识2013-03-17 16:51 1685人阅读 评论(0) 收藏 举报   目录(?)[+]   翻了下HTTP1.1的协 ...

  4. 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别

    详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别 http://blog.sina.com.cn/s/blog_686999de0100jgda.html   实例: ...

  5. python爬虫---详解爬虫分类,HTTP和HTTPS的区别,证书加密,反爬机制和反反爬策略,requests模块的使用,常见的问题

    python爬虫---详解爬虫分类,HTTP和HTTPS的区别,证书加密,反爬机制和反反爬策略,requests模块的使用,常见的问题 一丶爬虫概述       通过编写程序'模拟浏览器'上网,然后通 ...

  6. (转载)详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表

    郑重声明:原文转载于http://dengqi.blog.51cto.com/5685776/1223132 向好文章致敬!!! 一:MAC地址表详解 说到MAC地址表,就不得不说一下交换机的工作原理 ...

  7. 转载+++++iptables详解+++++转载

    转载:http://blog.chinaunix.net/uid-26495963-id-3279216.html 一.前言 防火墙,其实说白了讲,就是用于实现Linux下访问控制的功能的,它分为硬件 ...

  8. 【转载】详解CreateProcess调用内核创建进程的过程

    原文:详解CreateProcess调用内核创建进程的过程 昨天同学接到了腾讯的电面,有一题问到了CreateProcess创建进程的具体实现过程,他答得不怎么好吧应该是, 为了以防万一,也为了深入学 ...

  9. [转载]python 详解re模块

    原文地址:python 详解re模块作者:Rocky 正则表达式的元字符有. ^ $ * ? { [ ] | ( ) .表示任意字符 []用来匹配一个指定的字符类别,所谓的字符类别就是你想匹配的一个字 ...

随机推荐

  1. Eclipse EE遇到问题记录

    Eclipse EE可以进行Java web的开发,下面记录了使用Eclipse EE调试时遇到的一些问题. 1.tomcat启动timeout的设置,双击server,主窗口就变为了server的配 ...

  2. DaemonSet 典型应用场景 - 每天5分钟玩转 Docker 容器技术(129)

    Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本.DaemonSet 的不同之处在于:每个 Node 上最多只能运行一个副本. DaemonS ...

  3. java设计模式在公众号的应用——我是一个快乐的单例

    终于可以休息了,寻一把躺椅,安置于庭院,携一壶好茶,品一番风轻云淡... 自由自在的呼吸,伸手即可触摸阳光的温度,此时此刻,我就是我,像一个单例. 想起『设计模式』,就像想起了很久很久以前的故事,今日 ...

  4. JDBCTemplate与模板设计方法(二)

    前言:上一篇博客介绍了模板方法模式,并且给出了一个小demo,简单对模板方法进行了实现,接下来我们把目光转向spring的源码JDBCTemplate,看一看spring是如何对jdbc进行高度封装的 ...

  5. netty学习资源收集

    Netty学习笔记 Netty In Actions CSDN专栏 一起学Netty-CSDN专栏 Netty In Action中文版

  6. python学习:匿名函数

    Python 函数 lambda   匿名函数 -lambda 函数是一种快速定义单行的最小函数,可以用在任何需要函数的地方.   def fun(x,y):        return x*y fu ...

  7. 学习资料分享:Python能做什么?

    最近一直忙着研究学习Python,很久没更新博客了,整理了一些Python学习资料,和大家分享一下!每天更新一篇~ 一.Python 特点 1.易于学习:Python有相对较少的关键字,结构简单,和一 ...

  8. Mac下PyCharm快捷键大全

    Mac键盘符号和修饰键说明 ⌘ Command ⇧ Shift ⌥ Option ⌃ Control ↩︎ Return/Enter ⌫ Delete ⌦ 向前删除键(Fn+Delete) ↑ 上箭头 ...

  9. Conemu, Msys2 工具整合,提升windows下控制台工作效率

    与windows cmd相比较git-bash这类的console工具好用很多,但是git-bash的命令和功能相对简单,功能扩展起来不方便,git-bash本身也是基于msys的. 昨天发现使用Ms ...

  10. Yii2中DAO

    数据库访问 (DAO) 创建数据库连接 执行 SQL 查询 引用表和列名称 执行事务 复制和读写分离 操纵数据库模式 Yii 包含了一个建立在 PHP PDO 之上的数据访问层 (DAO).DAO为不 ...