Oracle 9i和10g中关于外连接的连接顺序的处理(重要)

本帖最后由 oraunix 于 2010-11-21 10:27 编辑

好好读读这篇文章,对于我们理解外连接非常重要。

在Oracle 9i中,外连接决定了连接的顺序,比如TEST1.id=TEST2.id(+),那么表TEST1一定是驱动表(即从表TEST1去join表TEST2),不管采用nest loop join或者hash join。因为外连接中,表TEST1的所有记录都要返回,所以必须用它来作为驱动表,如果反过来用表TEST2作为驱动表,实现比较困难,这个其实很好理解。
select a.* from TEST1 a,TEST2 b where a.id=b.id(+);
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 |  HASH JOIN OUTER         |       |
|   2 |   TABLE ACCESS FULL      | TEST1 |
|   3 |   TABLE ACCESS FULL      | TEST2 |
------------------------------------------
在10g版本中,Oracle引入了一个HASH JOIN RIGHT OUTER,可以改变外连接的顺序。Oracle会根据COST来决定hash join的顺序,比如Oracle发现表TEST2比TEST1小很多时,Oracle将会改变hash join的顺序,因为根据hash join的原理,选择比较小的表build hash table效率比较高。看看下面的执行计划:
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 |  HASH JOIN RIGHT OUTER   |       |
|   2 |   TABLE ACCESS FULL      | TEST2 |
|   3 |   TABLE ACCESS FULL      | TEST1 |
------------------------------------------
因为是外连接,TEST1表的记录需要全部返回,如果用TEST2表作为驱动表,Oracle一定做了特殊的处理。我们猜测一下Oracle如何处理,首先是采用普通的inner join的方法,将TEST2表在内存中build hash table(hash table A),然后扫描TEST1表,并进行hash join,如果TEST1表的记录未在hash table A中找到对应的记录,则在另外的内存区域记录(hash table B),然后将两个hash table合并,返回。这个特性仅适用于hash join,不适用于nest loop join,也没有hash join left outer.
我们再来看看anti join(反连接)的情况:
select a.* from TEST1 a WHERE not exists(select null from TEST2 b where a.id=b.id);
可以等价转换为外连接的形式
select a.* from TEST1 a,TEST2 b where a.id=b.id(+) and b.id is null;(只取TEST1表的内容)
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 |  HASH JOIN ANTI          |       |
|   2 |   TABLE ACCESS FULL      | TEST1 |
|   3 |   TABLE ACCESS FULL      | TEST2 |
------------------------------------------
select a.id,b.name from TEST1 a,TEST2 b where a.id=b.id(+) and b.id is null;(取两个表的内容)
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 |  FILTER                  |       |
|*  2 |   HASH JOIN OUTER        |       |
|   3 |    TABLE ACCESS FULL     | TEST1 |
|   4 |    TABLE ACCESS FULL     | TEST2 |
------------------------------------------
第一个SQL使用了反连接,因为它只需要返回TEST1表并且不在TEST2表中的记录,所以反连接在join的过程中,如果发现没有匹配的记录,只需要在TEST1表的hash table中作标记,然后返回即可。而第二个SQL需要返回两个表的记录,所以必须将TEST2表也build到hash table中,并且多了一个filter的工作(用来过滤b.id is null),所以反连接就是一种特殊的外连接,而且连接的顺序也是固定的,anti nest loop join的原理也是一样的。
再让我们看看HASH JOIN RIGHT OUTER用在反连接的环境中,我们又有新的发现,那就是HASH JOIN RIGHT ANTI.
select a.* from TEST1 a,TEST2 b where a.id=b.id(+) and b.id is null;(只取TEST1表中的内容)
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 |  HASH JOIN RIGHT ANTI    |       |
|   2 |   TABLE ACCESS FULL      | TEST2 |
|   3 |   TABLE ACCESS FULL      | TEST1 |
------------------------------------------
select a.id,b.name from TEST1 a,TEST2 b where a.id=b.id(+) and b.id is null;(取两个表的内容)
------------------------------------------
| Id  | Operation                | Name  |
------------------------------------------
|   0 | SELECT STATEMENT         |       |
|*  1 | FILTER                |       |
|*  2 |   HASH JOIN RIGHT OUTER  |       |
|   3 |    TABLE ACCESS FULL     | TEST2 |
|   4 |    TABLE ACCESS FULL     | TEST1 |
------------------------------------------
可以看到,虽然join的顺序发生了变化,但是如果我们只取inner table中的字段,就可以利用到反连接的特性。小小执行计划的改变,蕴含了更深层次的原理。

标签: 暂无标签
oraunix

写了 199 篇文章,拥有财富 1026,被 339 人关注

转播转播 分享分享 分享淘帖
回复

使用道具

成为第一个吐槽的人

您需要登录后才可以回帖 登录 | 加入社区

本版积分规则

意见
反馈