首發:
長沙快付
版權所有,未經許可嚴禁轉載
如果查詢涉及多張表,在優化器確定了每個表最恰當的訪問方法后,下一步就是確定將這些表聯結起來的最佳方法以及最恰當的順序。表之間的關系通過WHERE子句中的條件來定義,若未定義則會產生笛卡爾積。
聯結的方法有:嵌套循環聯結、排序-合并聯結、散列聯結及笛卡爾聯結。
每個聯結方法都會選擇一對表,所訪問的第一張表通常被稱為驅動表(the driving table),訪問的第二張表則被稱為內層表或被驅表(inner或driven-to table)。
優化器會根據WHERE子句篩選后得到的表行數進行估算,大小(塊、數據行及字節)最小的表通常被作為驅動表。
通俗來講,就是“小表驅動大表”。在FROM子句中,最小的表放在最后,作為驅動表來使用。“小表驅動大表”在兩表都沒有索引時常見,可能你跟我一樣認為小驅大或大驅小沒有差別。這里
長沙做網站為了方便理解舉個例子,但不一定正確,如:T1表有10行,T2表有10000行,1個塊可以放10行數據。那么,以T1為驅動,則T1掃描1個塊,內層T2掃描10次,共掃描塊為(1+1000*10=10001);以T2為驅動,則T2掃描1000個塊,內層T1掃描10000次,共掃描塊為(1000+1*10000=11000)。所以,你看到了,小表驅動大表掃描塊數更少。也就是說,表聯結時循環塊是固定的,主要差別在于掃描驅動表的塊數。
有兩種情況可能驅動表并非最小的表:
當優化器可以確定聯結的列在其中一張表基于UNIQUE或PRIMARY KEY約束時,即存在索引時,沒有索引的表將被作為驅動表。
當使用外聯結時,外聯結的表必須放在所聯結表的后面。
嵌套循環聯結
如果結果集大小有限并且聯結條件列在其中一表上是索引時采用這種方法最高效。嵌套循環聯結的運算成本主要是讀取外層表(驅動表)中的每一行并將其與所匹配的內層表中的行聯結所需要的成本(這個不好理解,還是理解我上面舉的例子吧)。
當數據行經過外層條件篩選并被確認匹配后,這些行就會逐個進入到內層循環。然后基于聯結列進行逐行檢查是否與被聯結表中的某一行相匹配,如果匹配就會被傳遞到查詢計劃的下一步或者如果沒有更多步驟直接被包含在最終結果集中。
這種聯結的強大之處在于使用的內存非常少,因為數據集一次只加工一行,所需要的開支也非常小。
在執行計劃中NESTED LOOPS表明使用了嵌套循環聯結。
下面舉例說明當聯結列在基本一表是索引時另一表作為驅動表的情形:
select empno,ename,dname,loc
from emp,dept
where emp.deptno = dept.deptno;
在本例中,盡管dept在FROM子句的最后,優化器也會選擇emp將作為驅動表(外層表),dept作為被驅表(內層表)。首先對emp表進行全表掃描,所有塊通過多塊讀方式讀出,再逐行訪問,并將聯結列(deptno)傳遞給內層循環針對dept表的查詢。對于內聯結,每一行在dept表的deptno列有匹配值的數據行都將被返回。對于外聯結,emp表的每一行都將被返回,dept表中無法匹配的列將用NULL值來填充。
優化器之所以不選擇dept表(即小又在FROM子句最后)作為驅動表,原因如下:
emp表在deptno列上沒有索引,訪問它的唯一方法就是全表掃描。如果將dept表選為驅動表,對于dept表中的每一行都要在emp表中進行全表掃描。但若使用emp表作為驅動表,只需要對emp進行一次全表掃描,對于dept表則采用deptno上的索引進行索引掃描。針對索引而言,deptno又正好是主鍵列,所以采用INDEX UNIQUE SCAN速度極快。
當然,我們也可以通過hint提示強制優化器使用某張表為驅動表,語法如下:
select /*+ ordered use_nl(dept emp) */ empno,ename,dname,loc
from emp,dept
where emp.deptno = dept.deptno;
排序-合并聯結
排序-合并聯結獨立地讀取需要聯結的兩張表,分別對表中數據行按聯結列進行排序,然后對排序后的行集進行合并。當然,在數據行排序前會經過WHERE子句篩選。
使用這種聯結方式對排序的開銷非常大,尤其是內存不足而使用臨時磁盤空間時。但一旦數據行排序完成,合并的過程是非??斓摹?/div>
在合并時,數據庫輪流操作兩個列表,比較最上面的數據行,丟棄在排序隊列中比另一個列表中的最上面的行出現得早的數據行,并只返回匹配的行。
在執行計劃中,MERGE JOIN表明使用了排序-合并聯結。
在對兩個表進行排序時,其實又加到了之前講過的數據訪問方式的選擇。一般而言,在聯結列在某表中正好是索引時,則可通過INDEX FULL SCAN來掃描,這樣將避免排序操作。沒有索引的表則只能通過全表掃描,然后進行排序了。
這種聯結方式適用于:
數據篩選條件有限并返回有限數據行的查詢;
沒有可用的更直接訪問數據的索引;
在條件為非等式的時候,如謂語有between等;
如果數據行源非常大,這種聯結方式可能是唯一可行的選擇;
同樣,我們可以通過hint提示強制使用這種聯結方式,如:
select /*+ ordered */ empno,ename,dname,loc
from emp,dept
where emp.deptno = dept.deptno;
散列聯結
前面講過,排序-合并聯結用來處理特定的非等式條件,而散列聯結則只有在等值聯結時才能進行。散列聯結與排序-合并聯結類似,建立散列表所需要的數據塊被讀取,然后剩下的工作將會針對放在內存或臨時磁盤空間的散列數據來進行。散列聯結具體工作方式如下:
首先,兩表都經過WHERE子句篩選得到行數據。然后基于表和索引的統計信息,被確定為返回最少行數的表被完全散列化(對聯結列運用hash函數)到內存中。這個散列表包含了原表的所有數據行并被基于聯結鍵的散列值(hash值)的隨機函數載入到“散列桶”中。只要有足夠的內存空間,這個散列表將一直放在內存中。如果沒有足夠的內存,散列表將會被寫入臨時磁盤空間。
其次,讀取另一張較大的表并對聯結鍵應用散列函數,然后利用得到的散列值跟內存中散列表進行匹配以尋找存放有相應行數據的“散列桶”。如果匹配成功,則返回這一行數據,否則丟棄。較大的表只讀取一次,并檢查其中的每一行來匹配。這與嵌套循環聯結不同之處在于此處內層表被多次讀?。▋葘颖肀欢啻巫x取的不應該是NESTED LOOP嗎,而HASH JOIN好像才是內層表只被讀一次吧)。
在執行計劃中,散列聯結用HASH JOIN來表示。
在散列聯結的執行計劃中,較小的散列表列在前面而探測表列在后面。決定哪個表最小的不僅取決于數據行數還取決于這些行的大小,因為整個行都必須要存放在散列表中。
當數據行源較大并且結果集也較大的情況下將更傾向于考慮散列聯結,或者如果要聯結的其中一張表確定總是返回同一數據行源,也可能會選用散列聯結因為這樣僅訪問一次這張表。如果在這種情況下選用嵌套循環聯結,這個數據行源就會被一遍一遍地訪問,需要比單獨訪問一次多做很多工作。最后,如果較小的表可以放到內存中,散列聯結也會很受歡迎。
笛卡爾聯結
笛卡爾聯結發生在當一張表中的所有行與另一張表中的所有行聯結的時候。
在執行計算中,MERGE JOIN CARTESIAN表明使用笛卡爾聯結。
笛卡爾聯結可能會導致得到很多重復行的結果集,此時若使用distinct雖然可以去除重復行,但代價極高,需要先排序再卻重。所以我們最好避免產生笛卡爾積。
外聯結
外聯結需要外聯結表作為驅動表,這意味著有可能不能選用更加優化的聯結執行順序。因此,使用外聯結要格外小心,因為它的選用有可能會影響到整個執行計劃的性能。