新聞中心
本文討論一下join技術(shù)背后的機制。我們知道常用的表連接有如下幾種
讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:國際域名空間、虛擬主機、營銷軟件、網(wǎng)站建設、鎮(zhèn)安網(wǎng)站維護、網(wǎng)站推廣。
笛卡爾連接
內(nèi)連接
左外連接
右外連接
全連接
這些sql的寫法,想必大家都很清楚了,那么這些連接的數(shù)據(jù)訪問是如何實現(xiàn)的呢?
nested loop
我們看如下查詢
SQL> alter session set optimizer_mode=rule; Session altered. SQL> select ename,dname from emp,dept where emp.deptno=dept.deptno; 14 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3625962092 ------------------------------------------------ | Id | Operation | Name | ------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | NESTED LOOPS | | | 3 | TABLE ACCESS FULL | EMP | |* 4 | INDEX UNIQUE SCAN | PK_DEPT | | 5 | TABLE ACCESS BY INDEX ROWID| DEPT | ------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
根據(jù)我們之前講的執(zhí)行計劃解讀,本查詢是這樣實現(xiàn)的:
全表掃描emp表(非阻塞掃描,并不是將數(shù)據(jù)全部取出,才執(zhí)行下一步)。
將emp中的數(shù)據(jù)逐條取出,通過索引PK_DEPT查詢出索引中的rowid,結(jié)果集變成(ename,rowid)
將2生成的結(jié)果集逐條取出,通過rowid去訪問dept表,結(jié)果集變成(ename,dname)
將結(jié)果集返回。
這種以循環(huán)的方式取出數(shù)據(jù)的join實現(xiàn)方式就叫嵌套循環(huán)。
此計劃可以用如下邏輯偽代碼實現(xiàn)
for y in (for x in (select * from emp)loop index lookup the rowid for x.deptno output joined record(ename,dept.rowid) end loop)loop select * from dept where rowid=y.rowid output joined record(ename,dname) end loop
我們把emp表稱之為驅(qū)動表(注驅(qū)動表與from子句的表順序無關(guān),主要看執(zhí)行計劃)。
此種連接方式,適用于驅(qū)動表返回數(shù)據(jù)比較少,并且被驅(qū)動表dept上deptno列有索引。如果查詢返回n行,那么dept表將被掃描n次。此連接擅長于從結(jié)果集中迅速取出第一行。
Hash Join
Hash Join適合處理大型結(jié)果集,優(yōu)化器選擇兩個表或者源數(shù)據(jù)中比較小的,使用join key在內(nèi)存中建立一個hash table。然后掃描大表,并探查hash表,去發(fā)現(xiàn)匹配的記錄。
小表稱為驅(qū)動表,大表稱為探查表
當hash table能全部放到內(nèi)存中,此種情況最好。如果內(nèi)存中放不下hash table,優(yōu)化器將hash table分區(qū),超出內(nèi)存范圍的分區(qū)將被寫到臨時表空間中。
我們分兩種情況討論hash join的實現(xiàn)
hash table 全部在內(nèi)存里
hash table是Oracle根據(jù)join key利用一個hash函數(shù)將小表分割成多個bucket。hash table建立完成后,Oracle去掃描大表,并且采用相同的hash算法,將讀入的數(shù)據(jù)也分割成多個bucket。bucket與bucket之間進行join運算,返回結(jié)果。直到大表讀完為止。
2. hash table 不能全部放到內(nèi)存中
這種情況有點麻煩,當Oracle發(fā)現(xiàn)內(nèi)存無法完全存放小表,Oracle在構(gòu)造hash table時,將小表進行分區(qū),每個分區(qū)中再構(gòu)造bucket 。當內(nèi)存寫滿時,Oracle將內(nèi)存中最大的分區(qū)寫到tempfile中。用這個方法,直到小表hash table構(gòu)造完成。此時,hashtable一部分數(shù)據(jù)在內(nèi)存,一部分數(shù)據(jù)在tempfile。
當Oracle去掃描大表時,如果掃描的行通過hash在內(nèi)存中能找到結(jié)果,就匹配成功。如果不能命中,則采用與hash table相同的分區(qū)方式,先將數(shù)據(jù)寫入tempfile中。當大表全部掃描完畢,hash table內(nèi)存中的部分已全部匹配完。此時依次將tempfile中的分區(qū)加載到內(nèi)存中。重新掃描大表臨時存在tempfile中的相應分區(qū)加以匹配。直到數(shù)據(jù)全部處理完。
SQL> insert into big_emp select * from big_emp; SQL> insert into big_emp select * from big_emp;#重復執(zhí)行多次 SQL> / 458752 rows created. SQL> create table dept_new as select * from dept; Table created. SQL> set autot traceonly SQL> select * from big_emp a,dept_new b where a.deptno=b.deptno; 917504 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1925493178 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 917K| 54M| 1490 (2)| 00:00:18 | |* 1 | HASH JOIN | | 917K| 54M| 1490 (2)| 00:00:18 | | 2 | TABLE ACCESS FULL| DEPT_NEW | 4 | 120 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| BIG_EMP | 917K| 28M| 1482 (1)| 00:00:18 | ------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("A"."DEPTNO"="B"."DEPTNO") Note ----- - dynamic sampling used for this statement (level=2) Statistics ---------------------------------------------------------- 4 recursive calls 1 db block gets 66338 consistent gets 0 physical reads 0 redo size 62512398 bytes sent via SQL*Net to client 673349 bytes received via SQL*Net from client 61168 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 917504 rows processed
Sort Merge Joins
排序合并連接與嵌套循環(huán)和散列連接都不同,排序合并連接沒有驅(qū)動表的概念。簡言之,排序合并將依次處理排序第一個輸入集,排序第二個輸入集,然后合并結(jié)果。排序合并通常不如散列高效,因為兩個結(jié)果集都需要排序,而散列連接在數(shù)據(jù)輸出前,只需處理一個結(jié)果集。排序合并通常在非等值連接中有效。即連接條件不是一個等式而是范圍比較(<或者>=). 或者是兩個表的數(shù)據(jù)已經(jīng)排好序啦。
我們看如下例子
SQL> set linesize 200 pagesize 200 SQL> set autot traceonly SQL> select a.ename,b.ename,a.hiredate,b.hiredate 2 from emp a,emp b 3 where a.empno<>b.empno and a.hiredate"B"."EMPNO") 5 - access("A"."HIREDATE"<"B"."HIREDATE") filter("A"."HIREDATE"<"B"."HIREDATE") Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 12 consistent gets 0 physical reads 0 redo size 3500 bytes sent via SQL*Net to client 578 bytes received via SQL*Net from client 7 SQL*Net roundtrips to/from client 2 sorts (memory) 0 sorts (disk) 90 rows processed
再看一個等值連接的
SQL> select ename,dname from emp a,dept b where a.deptno=b.deptno; 14 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 844388907 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 308 | 6 (17)| 00:00:01 | | 1 | MERGE JOIN | | 14 | 308 | 6 (17)| 00:00:01 | | 2 | TABLE ACCESS BY INDEX ROWID| DEPT | 4 | 52 | 2 (0)| 00:00:01 | | 3 | INDEX FULL SCAN | PK_DEPT | 4 | | 1 (0)| 00:00:01 | |* 4 | SORT JOIN | | 14 | 126 | 4 (25)| 00:00:01 | | 5 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("A"."DEPTNO"="B"."DEPTNO") filter("A"."DEPTNO"="B"."DEPTNO") Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 10 consistent gets 0 physical reads 0 redo size 819 bytes sent via SQL*Net to client 523 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 14 rows processed
網(wǎng)站欄目:Oracle學習之性能優(yōu)化(七)join的實現(xiàn)方式
當前鏈接:http://fisionsoft.com.cn/article/peedpd.html