在网上搜了下关于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数据库中,`IN`、`EXISTS`、`NOT IN` 和 `NOT EXISTS` 是四个常用的子查询操作符,它们在SQL查询语句中扮演着不同的角色,且各有其性能特点。以下是对这些操作符的详细分析和比较。 1. `IN` 操作符: `IN` ...
Oracle 中 EXISTS 和 IN 的效率问题详解 EXISTS 和 IN 都是 Oracle 中的集合操作符,但它们在使用和执行效率上有所不同。本文将深入探讨 EXISTS 和 IN 的使用场景、执行机制和效率问题。 EXISTS 的使用场景和机制 ...
IN、EXISTS、NOT IN、NOT EXISTS 是 SQL 中四种常用的条件判断运算符,它们之间的区别主要体现在使用场景、执行效率和语法结构上。 IN IN 是一种条件判断运算符,用于判断某个值是否存在于一个列表中。其基本语法...
### Oracle数据库中Exists与In的使用详解 #### 一、Exists 的使用方法 在Oracle数据库中,`EXISTS` 是一种常用的子查询操作符,用于判断子查询是否有结果返回。如果子查询至少返回一行数据,则 `EXISTS` 表达式的...
【描述】:文章并未直接提供描述,但从标题推测,该内容可能涉及数据库查询优化,特别是关于`not in`和`not exists`两种SQL查询子句的不同使用方式和性能比较。 【标签】:SQL优化,查询性能,数据库管理 【正文】...
在Oracle数据库中,`MINUS`、`IN` 和 `EXISTS` 是三种不同的SQL查询操作符,它们各自用于特定的数据处理场景。了解并熟练掌握这些操作符对于优化查询性能和编写复杂的SQL语句至关重要。 1. **MINUS 操作符** `MINUS...
在Oracle数据库中,`NOT EXISTS` 子句常用于查询中,以排除某些特定条件的记录。然而,它对查询性能的影响可能并不总是直观的,尤其是在涉及到外层查询时。本文将深入探讨`NOT EXISTS`如何影响外层查询,并通过示例...
在Oracle数据库中,`IN`和`EXISTS`都是用于子查询的比较操作符,但它们在执行方式和性能上存在显著差异。许多人误以为`EXISTS`总是比`IN`更快,但实际情况并非如此,具体表现取决于数据的分布和索引的利用。 1. **...
oracle 判断表 是否存在.亲自手写可用才上传的。
Oracle PL/SQL 开发人员试题...本资源涵盖了 Oracle PL/SQL 中的多个重要概念和语句,包括子查询、NOT IN 语句、EXISTS 语句、IN 语句和 ROWNUM 语句等。这些概念和语句是 Oracle PL/SQL 开发人员必须掌握的基本技能。
本文将详细地介绍 Oracle 语句优化的方法和技巧,涵盖多表关联、EXISTS 和 IN 语句的使用、INDEX_JOIN 优化、WHERE 和 HAVING 语句的区别、NOT IN 和 <> 操作符的使用、LIKE 操作符的替代、UNION 操作符的优化、SQL ...
### ORACLE转DB2对照全解 #### 一、Oracle SQL PL与DB2 inline SQL PL对比 本章节主要介绍Oracle SQL PL与DB2 inline SQL PL之间的对比,包括但不限于存储过程、触发器、用户定义函数(UDF)、条件语句及流程控制...
Oracle 和 SQL Server 是两种广泛应用的关系型数据库管理系统,它们在语法上有许多相似之处,但也存在显著的差异。在将 Oracle 数据库中的数据操作语言(DML)语句和过程化语言(PL/SQL)程序迁移至 SQL Server 时,...
- 结合使用`IN`和`NOT IN`可以精确控制查询条件,如`SELECT * FROM tb_name WHERE id IN (10,12,15,16) AND NOT id IN (21,22,23)`,这将返回id在第一个列表但不在第二个列表的记录。 2. `EXISTS/NOT EXISTS`: -...
8. **用NOT EXISTS替换NOT IN**:在否定子查询中,NOT EXISTS通常比NOT IN快,因为NOT EXISTS只需检查是否存在匹配项,而NOT IN可能需要遍历整个子查询结果。 这些技巧可以帮助数据库管理员和开发人员优化Oracle ...
14. **使用EXISTS替换IN,用NOT EXISTS替换NOT IN**:EXISTS和NOT EXISTS通常比IN和NOT IN更高效,尤其是在涉及子查询时。 15. **识别低效SQL语句**:监控和分析SQL执行计划,找出执行时间长、资源消耗大的语句进行...
NOT IN和NOT EXISTS的情况类似,需要根据具体场景选择合适的方法。 总的来说,这本手册提供了全面的Oracle数据库编程和调优实践指南,对于想要提升数据库性能的专业人士来说,是不可或缺的学习资料。它强调了正确...