关于in和exists

其实看到这个标题很多小伙伴就笑了,这个话题真的是永垂不朽,各种面试专用题目,从我刚开始接触Oracle的时候就开始有这个问题了,那时候还没什么人去研究这俩
所以基本你去百度和论坛搜索的话基本得到的一致答复就是exists比in快,而理由无非是exists可以用到索引,exists先走内查询等等等等。
但这些都是不完全正确的,对于初学可以暂时这样理解,但是我们始终是要成长的,不能总停留在别人的实验结果上,既要知其然,也要知其所以然。

这里我集各家之所长,加上自己的实验和理论也说下我的看法~

“in 和 exists 哪个快?” 如果有人这样问你, 你完全可以忽略这个问题,去跟他讲,in和exists本身并无快慢优劣,只是Oracle的处理机制有所差异而已,存在即是合理,所以不同场景下各有所长。

看下面这个图,这是我刚刚百度出来的结果:


这段话我们先不看他的对错,因为比起6年前的答案要好的太多,从“exists比in快,要用exists替换in的低效语句”已经进化到了开始从in和exists的执行方式来区分两者区别,已经不是一味的抛弃in。
这里的外表和内表听得比较奇怪,我们暂时改为主表和从表,好理解一些。
select .. from A where id in (select id from B)----A为主表,B为从表
所以网上的结论是:
1. in是 主从hash,exists是 主表作为驱动表NL从表
2. 因为in先查询从表,再查询主表,exists是先查询主表,再查询从表,基于驱动表要小的CBO思想,所以 in适合主表大、从表小;exists适合主表小而从表大的情况

但是我想说的是,这句话依旧不完全正确。
我们来看一组实验:
1. 创建实验用表
create table chen_dba_object as select * from dba_objects;
create table chen_user_object as select * from user_objects;

2. 根据网上结论写一个用in的SQL:

点击(此处)折叠或打开

  1. SQL> set linesize 999
  2. SQL> set autotrace traceonly;
  3. SQL> SELECT *
  4.   2 FROM CHEN_DBA_OBJECT T
  5.   3 WHERE T.OBJECT_ID IN
  6.   4 (SELECT A.OBJECT_ID FROM CHEN_USER_OBJECT A );

  7. 已选择11行。


  8. 执行计划
  9. ----------------------------------------------------------
  10. Plan hash value: 52863286

  11. -----------------------------------------------------------------------------------------
  12. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  13. -----------------------------------------------------------------------------------------
  14. | 0 | SELECT STATEMENT | | 12 | 2640 | 296 (2)| 00:00:04 |
  15. |* 1 | HASH JOIN RIGHT SEMI| | 12 | 2640 | 296 (2)| 00:00:04 |
  16. | 2 | TABLE ACCESS FULL | CHEN_USER_OBJECT | 12 | 156 | 3 (0)| 00:00:01 |
  17. | 3 | TABLE ACCESS FULL | CHEN_DBA_OBJECT | 68247 | 13M| 292 (2)| 00:00:04 |
  18. -----------------------------------------------------------------------------------------

  19. Predicate Information (identified by operation id):
  20. ---------------------------------------------------

  21.    1 - access("T"."OBJECT_ID"="A"."OBJECT_ID")

  22. Note
  23. -----
  24.    - dynamic sampling used for this statement (level=2)


  25. 统计信息
  26. ----------------------------------------------------------
  27.           0 recursive calls
  28.           0 db block gets
  29.        1042 consistent gets
  30.           0 physical reads
  31.           0 redo size
  32.        2179 bytes sent via SQL*Net to client
  33.         519 bytes received via SQL*Net from client
  34.           2 SQL*Net roundtrips to/from client
  35.           0 sorts (memory)
  36.           0 sorts (disk)
  37.          11 rows processed
可以看出确实是先扫描了从表,并且走了hash,速度也很快。

如果我SQL反过来写呢?

点击(此处)折叠或打开

  1. SQL> SELECT *
  2.   2 FROM CHEN_USER_OBJECT T
  3.   3 WHERE T.OBJECT_ID IN
  4.   4 (SELECT A.OBJECT_ID FROM CHEN_DBA_OBJECT A);

  5. 已选择11行。


  6. 执行计划
  7. ----------------------------------------------------------
  8. Plan hash value: 4193304006

  9. ---------------------------------------------------------------------------------------
  10. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  11. ---------------------------------------------------------------------------------------
  12. | 0 | SELECT STATEMENT | | 12 | 2436 | 295 (2)| 00:00:04 |
  13. |* 1 | HASH JOIN SEMI | | 12 | 2436 | 295 (2)| 00:00:04 |
  14. | 2 | TABLE ACCESS FULL| CHEN_USER_OBJECT | 12 | 2280 | 3 (0)| 00:00:01 |
  15. | 3 | TABLE ACCESS FULL| CHEN_DBA_OBJECT | 68247 | 866K| 291 (1)| 00:00:04 |
  16. ---------------------------------------------------------------------------------------

  17. Predicate Information (identified by operation id):
  18. ---------------------------------------------------

  19.    1 - access("T"."OBJECT_ID"="A"."OBJECT_ID")

  20. Note
  21. -----
  22.    - dynamic sampling used for this statement (level=2)


  23. 统计信息
  24. ----------------------------------------------------------
  25.           7 recursive calls
  26.           0 db block gets
  27.        1109 consistent gets
  28.           0 physical reads
  29.           0 redo size
  30.        2100 bytes sent via SQL*Net to client
  31.         519 bytes received via SQL*Net from client
  32.           2 SQL*Net roundtrips to/from client
  33.           0 sorts (memory)
  34.           0 sorts (disk)
  35.          11 rows processed
可以看出依旧是CHEN_USER_OBJECT(小表、主表)走了驱动表,所以在正常情况下,CBO评估哪个先扫描表,与IN、Exists无关,CBO是基于成本的优化器,所以和写法无关。

3.  根据网上结论写一个用exists的SQL:

点击(此处)折叠或打开

  1. SQL> alter system flush buffer_cache;

  2. 系统已更改。

  3. SQL>
  4. SQL> SELECT *
  5.   2 FROM CHEN_USER_OBJECT T
  6.   3 WHERE exists
  7.   4 (SELECT 1 FROM CHEN_DBA_OBJECT A where a.object_id = t.object_id);

  8. 已选择11行。


  9. 执行计划
  10. ----------------------------------------------------------
  11. Plan hash value: 4193304006

  12. ---------------------------------------------------------------------------------------
  13. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  14. ---------------------------------------------------------------------------------------
  15. | 0 | SELECT STATEMENT | | 12 | 2436 | 295 (2)| 00:00:04 |
  16. |* 1 | HASH JOIN SEMI | | 12 | 2436 | 295 (2)| 00:00:04 |
  17. | 2 | TABLE ACCESS FULL| CHEN_USER_OBJECT | 12 | 2280 | 3 (0)| 00:00:01 |
  18. | 3 | TABLE ACCESS FULL| CHEN_DBA_OBJECT | 68247 | 866K| 291 (1)| 00:00:04 |
  19. ---------------------------------------------------------------------------------------

  20. Predicate Information (identified by operation id):
  21. ---------------------------------------------------

  22.    1 - access("A"."OBJECT_ID"="T"."OBJECT_ID")

  23. Note
  24. -----
  25.    - dynamic sampling used for this statement (level=2)


  26. 统计信息
  27. ----------------------------------------------------------
  28.           0 recursive calls
  29.           0 db block gets
  30.        1042 consistent gets
  31.        1040 physical reads
  32.           0 redo size
  33.        2100 bytes sent via SQL*Net to client
  34.         519 bytes received via SQL*Net from client
  35.           2 SQL*Net roundtrips to/from client
  36.           0 sorts (memory)
  37.           0 sorts (disk)
  38.          11 rows processed

好像没有走网上说的NestLoop呃 (⊙o⊙)…

4. 写个能走NL的SQL:

点击(此处)折叠或打开

  1. SQL> SELECT *
  2.   2 FROM CHEN_USER_OBJECT T
  3.   3 WHERE T.OBJECT_ID IN (SELECT A.OBJECT_ID FROM CHEN_DBA_OBJECT A where rownum = 1);

  4. 未选定行


  5. 执行计划
  6. ----------------------------------------------------------
  7. Plan hash value: 897367281

  8. -----------------------------------------------------------------------------------------
  9. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  10. -----------------------------------------------------------------------------------------
  11. | 0 | SELECT STATEMENT | | 1 | 203 | 5 (0)| 00:00:01 |
  12. | 1 | NESTED LOOPS | | 1 | 203 | 5 (0)| 00:00:01 |
  13. | 2 | VIEW | VW_NSO_1 | 1 | 13 | 2 (0)| 00:00:01 |
  14. |* 3 | COUNT STOPKEY | | | | | |
  15. | 4 | TABLE ACCESS FULL| CHEN_DBA_OBJECT | 68247 | 866K| 2 (0)| 00:00:01 |
  16. |* 5 | TABLE ACCESS FULL | CHEN_USER_OBJECT | 1 | 190 | 3 (0)| 00:00:01 |
  17. -----------------------------------------------------------------------------------------

  18. Predicate Information (identified by operation id):
  19. ---------------------------------------------------

  20.    3 - filter(ROWNUM=1)
  21.    5 - filter("T"."OBJECT_ID"="OBJECT_ID")

  22. Note
  23. -----
  24.    - dynamic sampling used for this statement (level=2)


  25. 统计信息
  26. ----------------------------------------------------------
  27.          49 recursive calls
  28.           0 db block gets
  29.         543 consistent gets
  30.           0 physical reads
  31.           0 redo size
  32.        1277 bytes sent via SQL*Net to client
  33.         508 bytes received via SQL*Net from client
  34.           1 SQL*Net roundtrips to/from client
  35.           0 sorts (memory)
  36.           0 sorts (disk)
  37.           0 rows processed
所以其实到底走NL还是HASH没固定死是in还是exists,一切路径都是基于代价的(CBO)。

那么问题来了,到底哪个快哪个慢呢?
问题在于用in和exists哪个可以是子查询展开,哪个就快。
一般情况下,使用in和exists必定用到了子查询,所以子查询能否展开进行查询重写是优化in/exists的最关键的一步

一般情况下子查询都会展开,但当子查询中的SQL被CBO认为是一个整体、无法展开的时候:无论SQL中是in还是exists,从表都会作为一个整体被NL。
可以看到上面第4步中使用了关键字rownum,执行计划中出现view关键字 ,说明子查询无法展开,从而走了NestLoop嵌套循环

除了rownum还有类似树形查询level、分析函数rollup、cube等,子查询中的内容要作为整体无法与主表合并关联的时候,都会走循环查询。

5. exists中恐怖的filter

点击(此处)折叠或打开

  1. SQL> SELECT *
  2.   2 FROM CHEN_DBA_OBJECT T
  3.   3 WHERE exists
  4.   4 (SELECT 1 FROM CHEN_USER_OBJECT A where a.object_id = t.object_id and rownum = 1);

  5. 已选择11行。


  6. 执行计划
  7. ----------------------------------------------------------
  8. Plan hash value: 128792432

  9. ----------------------------------------------------------------------------------------
  10. | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
  11. ----------------------------------------------------------------------------------------
  12. | 0 | SELECT STATEMENT | | 32 | 6624 | 6695 (1)| 00:01:21 |
  13. |* 1 | FILTER | | | | | |
  14. | 2 | TABLE ACCESS FULL | CHEN_DBA_OBJECT | 68247 | 13M| 292 (2)| 00:00:04 |
  15. |* 3 | COUNT STOPKEY | | | | | |
  16. |* 4 | TABLE ACCESS FULL| CHEN_USER_OBJECT | 1 | 13 | 3 (0)| 00:00:01 |
  17. ----------------------------------------------------------------------------------------

  18. Predicate Information (identified by operation id):
  19. ---------------------------------------------------

  20.    1 - filter( EXISTS (SELECT 0 FROM "CHEN_USER_OBJECT" "A" WHERE ROWNUM=1 AND
  21.               "A"."OBJECT_ID"=:B1))
  22.    3 - filter(ROWNUM=1)
  23.    4 - filter("A"."OBJECT_ID"=:B1)

  24. Note
  25. -----
  26.    - dynamic sampling used for this statement (level=2)


  27. 统计信息
  28. ----------------------------------------------------------
  29.          13 recursive calls
  30.           0 db block gets
  31.      218721 consistent gets
  32.           0 physical reads
  33.           0 redo size
  34.        2179 bytes sent via SQL*Net to client
  35.         519 bytes received via SQL*Net from client
  36.           2 SQL*Net roundtrips to/from client
  37.           0 sorts (memory)
  38.           0 sorts (disk)
  39.          11 rows processed
exists中子查询无法展开的时候不会走NestLoop,而是走了filter,如果是小表作为驱动,则没什么太大影响,可以理解为filter就是NestLoop。
但是filter有个比较坑爹特性就是无法修改驱动表,hint也无法修改,永远会先访问主表(也就是网上说的外表),在对从表(内表)访问,然后走filter关联。

filter这个操作在《Cost Based Oracle Fundamental》此书第九章有介绍。filter的操作是对外表的每一行,都要对内表执行一次全表扫描,所以很多时候提到filter都会感到可怕。他其实很像我们熟悉的neested loop,但它的独特之处在于会维护一个hash table。具体不在这里赘述。
ps. 对了 谁要是买了这个书能否借我看2天,这本书真的是太贵了 =。=

所以结论是:在相对较高的Oracle版本中,在现在基本都用11g02里,其实in比exists更好些,因为in至少不会走这蛋疼的filter。
如果出现了filter就一定要想办法处理,加hint不行的情况就要改写SQL,
通常我们可以用外关联或者with as来手动去掉视图,然后再关联过滤,这样可以避免CBO走filter




版权声明:本文为博主原创文章,未经博主允许不得转载。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26224278/viewspace-1810899/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/26224278/viewspace-1810899/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值