Join的實現算法有三種,分別是Nested Loops Join, Merge Join, Hash Join。
DB二、SQL Server和Oracle都是使用這三種方式,不過Oracle選擇使用nested loop的條件跟SQL Server有點差異,內存管理機制跟SQL Server不同,所以查看執行計劃,Oracle中nested loops運用很是多,而merge和hash方式相對較少,SQL Server中,merge跟hash方式則是很是廣泛。
一.Nested Loopsb Join
1.定義
Nested Loops也稱爲嵌套迭代,它將一個聯接輸入用做外部輸入表(顯示爲圖形執行計劃中的頂端輸入),將另外一個聯接輸入用做內部(底端)輸入表。外部循環逐行消耗外部輸入表。內部循環爲每一個外部行執行,在內部輸入表中搜索匹配行。最簡單的狀況是,搜索時掃描整個表或索引;這稱爲單純嵌套循環聯接。若是搜索時使用索引,則稱爲索引嵌套循環聯接。若是將索引生成爲查詢計劃的一部分(並在查詢完成後當即將索引破壞),則稱爲臨時索引嵌套循環聯接。僞碼錶示以下:算法
for each row R1 in the outer table
for each row R2 in the inner table
if R1 joins with R2
return (R1, R2)數據庫
2.應用場景
適用於outer table(有的地方叫Master table)的記錄集比較少(<10000)並且inner table(有的地方叫Detail table)索引選擇性較好的狀況下(inner table要有index)。
inner table被outer table驅動,outer table返回的每一行都要在inner table中檢索到與之匹配的行。固然也能夠用ORDERED 提示來改變CBO默認的驅動表,使用USE_NL(table_name1 table_name2)但是強制CBO 執行嵌套循環鏈接。服務器
cost = outer access cost + (inner access cost * outer cardinality)數據結構
3.經常使用於執行的鏈接
Nested Loops常執行Inner Join(內部聯接)、Left Outer Join(左外部聯接)、Left Semi Join(左半部聯接)和Left Anti Semi Join(左反半部聯接)邏輯操做。
Nested Loops一般使用索引在內部表中搜索外部表的每一行。根據預計的開銷,Microsoft SQL Server決定是否對外部輸入進行排序來改變內部輸入索引的搜索位置。
將基於所執行的邏輯操做返回全部知足 Argument 列內的(可選)謂詞的行。
二.Merge Join
1.定義
Merge Join第一個步驟是確保兩個關聯表都是按照關聯的字段進行排序。若是關聯字段有可用的索引,而且排序一致,則能夠直接進行Merge Join操做;不然,SQL Server須要先對關聯的表按照關聯字段進行一次排序(就是說在Merge Join前的兩個輸入上,可能都須要執行一個Sort操做,再進行Merge Join)。
兩個表都按照關聯字段排序好以後,Merge Join操做從每一個表取一條記錄開始匹配,若是符合關聯條件,則放入結果集中;不然,將關聯字段值較小的記錄拋棄,從這條記錄對應的表中取下一條記錄繼續進行匹配,直到整個循環結束。
在多對多的關聯表上執行Merge Join時,一般須要使用臨時表進行操做。例如A join B使用Merge Join時,若是對於關聯字段的某一組值,在A和B中都存在多條記錄A一、A2...An、B一、B2...Bn,則爲A中每一條記錄A一、A2...An,都必須在B中對全部相等的記錄B一、B2...Bn進行一次匹配。這樣,指針須要屢次從B1移動到Bn,每一次都須要讀取相應的B1...Bn記錄。將B1...Bn的記錄預先讀出來放入內存臨時表中,比從原數據頁或磁盤讀取要快。
2.應用場景另
用在數據沒有索引可是已經排序的狀況下。
一般狀況下hash join的效果都比Sort merge join要好,然而若是行源已經被排過序,在執行排序合併鏈接時不須要再排序了,這時Sort merge join的性能會優於hash join。可使用USE_MERGE(table_name1 table_name2)來強制使用Sort merge join。
cost = (outer access cost * # of hash partitions) + inner access cost
3.經常使用於執行的鏈接
Merge Join常執行Inner Join(內部聯接)、Left Outer Join(左外部聯接)、Left Semi Join(左半部聯接)、Left Anti Semi Join(左反半部聯接)、Right Outer Join(右外部聯接)、Right Semi Join(右半部聯接)、Right Anti Semi Join(右反半部聯接)和Union(聯合)邏輯操做。
在 Argument 列中,若是操做執行一對多聯接,則 Merge Join 運算符將包含 MERGE:() 謂詞;若是操做執行多對多聯接,則該運算符將包含 MANY-TO-MANY MERGE:() 謂詞。Argument 列還包含一個用於執行操做的列的列表,該列表以逗號分隔。Merge Join 運算符要求在各自的列上對兩個輸入進行排序,這能夠經過在查詢計劃中插入顯式排序操做來實現。若是不須要顯式排序(例如,若是數據庫內有合適的 B 樹索引或能夠對多個操做(如合併聯接和對彙總分組)使用排序順序),則合併聯接尤爲有效。
三.Hash Join
1.定義
Hash Match有兩個輸入:build input(也叫作outer input)和probe input(也叫作inner input),不只用於inner/left/right join等,象union/group by等也會使用hash join進行操做,在group by中build input和probe input都是同一個記錄集。
Hash Match操做分兩個階段完成:Build(構造)階段和Probe(探測)階段。
Build(構造)階段主要構造哈希表(hash table)。在inner/left/right join等操做中,表的關聯字段做爲hash key;在group by操做中,group by的字段做爲hash key;在union或其它一些去除重複記錄的操做中,hash key包括全部的select字段。
Build操做從build input輸入中取出每一行記錄,將該行記錄關聯字段的值使用hash函數生成hash值,這個hash值對應到hash table中的hash buckets(哈希表目)。若是一個hash值對應到多個hash buckts,則這些hash buckets使用鏈表數據結構鏈接起來。當整個build input的table處理完畢後,build input中的全部記錄都被hash table中的hash buckets引用/關聯了。
Probe(探測)階段,SQL Server從probe input輸入中取出每一行記錄,一樣將該行記錄關聯字段的值,使用build階段中相同的hash函數生成hash值,根據這個hash值,從build階段構造的hash table中搜索對應的hash bucket。hash算法中爲了解決衝突,hash bucket可能會連接到其它的hash bucket,probe動做會搜索整個衝突鏈上的hash bucket,以查找匹配的記錄。
若是build input記錄數很是大,構建的hash table沒法在內存中容納時,SQL Server分別將build input和probe input切分紅多個分區部分(partition),每一個partition都包括一個獨立的、成對匹配的build input和probe input,這樣就將一個大的hash join切分紅多個獨立、互相不影響的hash join,每個分區的hash join都可以在內存中完成。SQL Server將切分後的partition文件保存在磁盤上,每次裝載一個分區的build input和probe input到內存中,進行一次hash join。這種hash join叫作Grace Hash join,使用的Grace Hash Join算法。
2.應用場景
適用於兩個表的數據量差異很大。但須要注意的是:若是HASH表太大,沒法一次構造在內存中,則分紅若干個partition,寫入磁盤的temporary segment,則會多一個I/O的代價,會下降效率,此時須要有較大的temporary segment從而儘可能提升I/O的性能。
能夠用USE_HASH(table_name1 table_name2)提示來強制使用散列鏈接。若是使用散列連HASH_AREA_SIZE 初始化參數必須足夠的大,若是是9i,Oracle建議使用SQL工做區自動管理,設置WORKAREA_SIZE_POLICY 爲AUTO,而後調整PGA_AGGREGATE_TARGET 便可。
也可使用HASH_JOIN_ENABLED=FALSE(默認爲TRUE)強制不使用hash join。
cost = (outer access cost * # of hash partitions) + inner access cost
3.經常使用於執行的連接
Hash Match運算符經過計算其生成輸入中每行的哈希值生成哈希表。HASH:()謂詞以及一個用於建立哈希值的列的列表出如今Argument列內。而後,該謂詞爲每一個探測行(若是適用)使用相同的哈希函數計算哈希值並在哈希表內查找匹配項。若是存在殘留謂詞(由 Argument 列中的 RESIDUAL:() 標識),則還須知足此殘留謂詞,只有這樣行才能被視爲是匹配項。行爲取決於所執行的邏輯操做:
(1)對於聯接,使用第一個(頂端)輸入生成哈希表,使用第二個(底端)輸入探測哈希表。按聯接類型規定的模式輸出匹配項(或不匹配項)。若是多個聯接使用相同的聯接列,這些操做將分組爲一個哈希組。
(2)對於非重複或聚合運算符,使用輸入生成哈希表(刪除重複項並計算聚合表達式)。生成哈希表時,掃描該表並輸出全部項。
(3)對於 union 運算符,使用第一個輸入生成哈希表(刪除重複項)。使用第二個輸入(它必須沒有重複項)探測哈希表,返回全部沒有匹配項的行,而後掃描該哈希表並返回全部項。
四.性能分析
Hash join的主要資源消耗在於CPU(在內存中建立臨時的hash表,並進行hash計算),而merge join的資源消耗主要在於磁盤I/O(掃描表或索引)。在並行系統中,hash join對CPU的消耗更加明顯。因此在CPU緊張時,最好限制使用hash join。
在絕大多數狀況下,hash join效率比其餘join方式效率更高:
在Sort-Merge Join(SMJ),兩張表的數據都須要先作排序,而後作merge。所以效率相對最差;
Nested-Loop Join(NL)效率比SMJ更高。特別是當驅動表的數據量很大(集的勢高)時。這樣能夠並行掃描內表。
Hash join效率最高,由於只要對兩張表掃描一次,Merge Join(合併聯接)自己的速度很快,但若是須要排序操做,選擇合併聯接就會很是費時。然而,若是數據量很大且可以從現有 B 樹索引中得到預排序的所需數據,則合併聯接一般是最快的可用聯接算法。若是是無序的數據,Merge Join首先作的是排序,若是數據量大,排序就會溢出到tempdb, 效率就將低了。
若是外部輸入很小(<10000)而內部輸入很大且預先建立了索引,則Nested Loops(嵌套循環聯接)尤爲有效。在許多小事務中(如那些隻影響較小的一組行的事務),索引嵌套循環聯接遠比合並聯接和哈希聯接優越。但在大查詢中,嵌套循環聯接一般不是最佳選擇。
若是兩個表的數據量差異很大,則使用Hash Match。但須要注意的是:若是HASH表太大,沒法一次構造在內存中,則分紅若干個partition,寫入磁盤的temporary segment,則會多一個I/O的代價,會下降效率,此時須要有較大的temporary segment從而儘可能提升I/O的性能。Hash join的主要資源消耗在於CPU(在內存中建立臨時的HASH表,並進行HASH計算),而Merge join的資源消耗主要在於磁盤I/O(掃描表或索引)。
五.優化原則
1.如有單行謂詞,則他的表必定是驅動表(select * from employees e,departments d where e.department_id=d.department_id and e.department_id=100 and salary=10000; 上面的語句中e.department_id=d.department_id是鏈接謂詞,e.department_id=100是非鏈接謂詞(對鏈接列的限制),salary=10000是單行謂詞(對非鏈接列的限制))
2.外鏈接時,必定是用顯示的行數比較多的那個表做爲驅動表。如:
select e.employee_id,e.department_id,d.manager_id,d.location_id from employees e right join departments d on e.department_id=d.department_id
則departments表顯示的行數必定大於等於employees表,因此應該要以departments表做爲驅動表,若是以employees表做爲驅動表,則departments表中多顯示的那幾行就顯示不出來了
4.通常狀況下,Hash Join處理代價很是高,是數據庫服務器內存和CPU的頭號殺手之一,尤爲是涉及到分區(數據量太大致使內存不夠的狀況,或者併發訪問很高致使當前處理線程沒法得到足夠的內存,那麼數據量不是特大的狀況下也可能須要進行分區),爲了儘快的完成全部的分區步驟,將使用大量異步的I/O操做,所以期間單一一個線程就可能致使多個磁盤驅動器出於忙碌狀態,這頗有可能阻塞其它線程的執行。
5. 要避免大數據的Hash Join,儘可能將其轉化爲高效的Merge Join、Nested Loops。可能使用的手段有表結構設計、索引調整設計、SQL優化,以及業務設計優化。例如冗餘字段的運用,將統計分析結果用service按期跑到靜態表中,適當的冗餘表,使用AOP或相似機制同步更新等。
6. 儘可能減小join兩個輸入端的數據量。這一點比較常犯的毛病是,條件不符合SARG((Searchable Arguments),在子查詢內部條件給的不充分(SQL過於複雜狀況下SQL Server查詢優化器常常犯傻,寫在子查詢外部的條件不會被用在子查詢內部,影響子查詢內部的效率或者是跟子查詢再join時候的效率)。
併發