ORACLE多表连接分为三大类:NEXT LOOP、SORT MERGE、HASH JOIN。
每一类又分为三小类,有传统连接,Semi Join, Anti Join。(后两种叫做半连接)
NEST LOOP方式:
有两个表,驱动表Driving Table,被驱动表Driven Table。
驱动表做一次遍历,被驱动表做多次遍历。
返回第一条记录速度很快,不需要排序。
可以使用非等值连接。
SORT MERGE方式:
两个表地位一样。每个表都要先排序,然后进行合并,返回记录集。
排序首先在内存中进行,能在内存中完成的叫做Optimal Sort,也叫In-Memory Sort。如果需要借助磁盘缓冲,叫做外部排序External Sort。
在外部排序中,运行run是指一次对磁盘做IO。
如果一次输入就能完成整个数据集的排序叫做1路排序1-Pass Sort。需要多次输入输出操作的叫多路排序Multi-Pass Sort。
从性能角度来看Optimal Sort>1-Pass Sort>Multi-Pass Sort
执行计划中
OMem:代表使用Optimal排序需要的内存估量。
1Mem:代表使用1-Pass排序需要的内存估量。
O/1/M:代表实际Optimal、1-Pass、Multi-Pass方式的执行次数。
HASH JOIN方式:
一个驱动表,一个被驱动表。过程有两个阶段:
准备阶段:对驱动表的连接字段进行哈希操作,产生一系列的Hash Bucket(哈希桶)
探测阶段:依次上去被驱动表每条记录,对连接字段执行相同哈希函数,和驱动表哈希桶进行匹配,这个过程叫探测(Probe)。
几种方式比较:
ORACLE实现排序都是用二叉树插入排序算法(Binary Insertion Tree)。
内存中的INDEX中,每个节点对应一条记录,每个节点还保存一个父节点和两个子节点的指针。这样在32位系统中,这个开销是12字节,64位系统中,这个开销是24字节。
排序过程是内存和CPU的双重密集操作。
完全内存排序有时候不必磁盘排序快。
如果CPU是资源瓶颈,IO比较空闲,应该减少排序空间大小,使用1-Pass Sort。尤其是在创建索引时,通过减少SORT_AREA_SIZE来提升性能。因为内存排序和磁盘排序,记录比较操作相差不大,但是内存排序中,二叉树可能过高,CPU资源消耗太大。
HASH JOIN内存消耗远小于SORT MERGE,也不需要密集的CPU操作。所以HASH JOIN算法普遍优于SORT MERGE算法。
如果查询关注的是整个记录而不畅部分记录时,HASH JOIN非常类似NEST LOOP,但优于NEST LOOP,因为HASH TABLE构建在PGA中,不需要LATCH保护。
半连接
是针对IN, EXISTS, NOT IN, NOT EXISTS的变形。
子查询在FROM里的叫做IN-LINE VIEW,在WHERE子句中的叫NESTED SUBQUERY(嵌套子查询)。IN, EXISTS, NOT IN, NOT EXISTS都属于嵌套子查询。
对于嵌套子查询,ORACLE处理有两种方式:展开子查询,不展开子查询。
对于嵌套视图,ORACLE处理方法有两种,合并,不合并。
ORACLE 10G以前的优化器会在Optimization之前就展开,不做成本评估。
In、Exists展开结果是变成Semi-Join。Not Exists和Not In是转换成Anti-Join。
对于Inline-View或者其它View,Oracle也会尝试合并到主查询中,这个动作叫做Merge,对应hint是和。这个在执行计划中进行确认就可以。也就是,如果没有VIEW字样,就是发生了MERGE合并;有VIEW字样,就是没有做MERGE合并。
对于子查询展开,这个过程叫做Subquery Unnesting。
Merge和Unnest不同的地方是,对于Distinct、Group by这些子句,Merge可以合并,叫做Complex View Merge,Set和Unnest一样,不能合并。
缺省时,不进行Complex View Merge。使用才能达到Merge效果。
子查询合并到主查询中,好处是优化器可以通判考虑访问路径方式。否则,ORACLE只能针对外层内存查询分别优化。而且可以利用ORACLE提供的Semi-Join、Anti-Join两种连接方式。
不是所有子查询都可以展开,例如,connect by, start with, rownum伪列, set操作符(UNION、UNION ALL、MINUS、INTERSECT)、聚集函数(SUM、COUNT、GROUP BY)不会被展开。
半连接关注重点在于:对于外表某个记录,在内表中找到一个匹配记录就返回外表记录。
不展开查询:
类似NEST LOOP方式,对主查询每条记录都执行一次子查询,在执行计划中叫做FILTER。
ORACLE 10G中,使用提示。(这个是非半连接)
SQL>SELECT ID
FROM A
WHERE EXISTS ( SELECT 1 FROM B WHERE A.ID=B.P_ID);
展开子查询:
SQL>SELECT ID
FROM A
WHERE EXISTS ( SELECT 1 FROM B WHERE A.ID=B.P_ID);
此时执行计划中,会看到HASH JOIN SEMI字样,说明这是一个半连接。
好处是:
对于A表中一条记录,发现B中匹配一条就停止扫描B,转而处理A的下一条记录。
返回结果无需去重,即使A和B记录时1:n,表A每个记录只会返回一次。
从ORACLE 9i开始IN和EXISTS已经没有区别了,执行计划是一样的。
SEMI JOIN的HINT如下:
EXISTS:
SQL>SELECT ID
FROM A
WHERE EXISTS (SELECT 1 FROM B WHERE A.ID=B.P_ID);
IN:
SQL>SELECT ID
FROM A
WHERE IN (SELECT 1 FROM B WHERE A.ID=B.P_ID);
NOT EXISTS:默认就使用展开的ANTI-JOIN
SQL>SELECT ID
FROM A
WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.ID=B.P_ID);
NOT IN:
与NOT EXISTS的区别在于处理NULL
NOT IN查看子结果中有没有NULL,如果有NULL,返回FALSE;NOT EXISTS不关心有没有NULL,只关心记录数,如果有记录,返回FALSE。
NOT IN可能在匹配列上,引起性能问题,原因是索引失效。
HINT:
操作
Nest Loop
Hash Join
Sort Mereg
Join
USE_NL
USE_HASH
USE_MERGE
Anti Join
NL_AJ
HASH_AJ
MERGE_AJ
Semi
NL_SJ
HASH_SJ
MERGE_SJ