SQL優化:Merge Join vs. Hash Join vs. Nested Loop

本文轉發自技術世界原文連接 http://www.jasongj.com/2015/03/07/Join1/sql

Nested Loop,Hash Join,Merge Join介紹

  • Nested Loop: 對於被鏈接的數據子集較小的狀況,Nested Loop是個較好的選擇。Nested Loop就是掃描一個表(外表),每讀到一條記錄,就根據Join字段上的索引去另外一張表(內表)裏面查找,若Join字段上沒有索引查詢優化器通常就不會選擇 Nested Loop。在Nested Loop中,內表(通常是帶索引的大表)被外表(也叫「驅動表」,通常爲小表——不緊相對其它表爲小表,並且記錄數的絕對值也較小,不要求有索引)驅動,外表返回的每一行都要在內表中檢索找到與它匹配的行,所以整個查詢返回的結果集不能太大(大於1 萬不適合)。oop

  • Hash Join: Hash Join是作大數據集鏈接時的經常使用方式,優化器使用兩個表中較小(相對較小)的表利用Join Key在內存中創建散列表,而後掃描較大的表並探測散列表,找出與Hash表匹配的行。 這種方式適用於較小的表徹底能夠放於內存中的狀況,這樣總成本就是訪問兩個表的成本之和。可是在表很大的狀況下並不能徹底放入內存,這時優化器會將它分割成若干不一樣的分區,不能放入內存的部分就把該分區寫入磁盤的臨時段,此時要求有較大的臨時段從而儘可能提升I/O 的性能。它可以很好的工做於沒有索引的大表和並行查詢的環境中,並提供最好的性能。大多數人都說它是Join的重型升降機。Hash Join只能應用於等值鏈接(如WHERE A.COL3 = B.COL4),這是由Hash的特色決定的。性能

  • Merge Join: 一般狀況下Hash Join的效果都比排序合併鏈接要好,然而若是兩表已經被排過序,在執行排序合併鏈接時不須要再排序了,這時Merge Join的性能會優於Hash Join。Merge join的操做一般分三步:   1. 對鏈接的每一個表作table access full;   2. 對table access full的結果進行排序。   3. 進行merge join對排序結果進行合併。 在全表掃描比索引範圍掃描再進行表訪問更可取的狀況下,Merge Join會比Nested Loop性能更佳。當表特別小或特別巨大的時候,實行全表訪問可能會比索引範圍掃描更有效。Merge Join的性能開銷幾乎都在前兩步。Merge Join可適於於非等值Join(>,<,>=,<=,可是不包含!=,也即<>)大數據

Nested Loop,Hash JOin,Merge Join對比

類別 Nested Loop Hash Join Merge Join
使用條件 任何條件 等值鏈接(=) 等值或非等值鏈接(>,<,=,>=,<=),‘<>’除外
相關資源 CPU、磁盤I/O 內存、臨時空間 內存、臨時空間
特色 當有高選擇性索引或進行限制性搜索時效率比較高,可以快速返回第一次的搜索結果。 當缺少索引或者索引條件模糊時,Hash Join比Nested Loop有效。一般比Merge Join快。在數據倉庫環境下,若是表的紀錄數多,效率高。 當缺少索引或者索引條件模糊時,Merge Join比Nested Loop有效。非等值鏈接時,Merge Join比Hash Join更有效
缺點 當索引丟失或者查詢條件限制不夠時,效率很低;當表的紀錄數多時,效率低。 爲創建哈希表,須要大量內存。第一次的結果返回較慢。 全部的表都須要排序。它爲最優化的吞吐量而設計,而且在結果沒有所有找到前不返回數據。

實驗

本文所作實驗均基於PostgreSQL 9.3.5平臺優化

小於萬條記錄小表與大表Join

一張記錄數1萬如下的小表nbar.mse_test_test,一張大表165萬條記錄的大表nbar.nbar_test,大表上建有索引設計

**Query 1:**等值Join

select 
    count(*)
from 
    mse_test_test, 
    nbar_test 
where 
    mse_test_test.client_key = nbar_test.client_key;

Query 1 Test 1: 查詢優化器自動選擇Nested Loop,耗時784.845 ms

Nested loop

  以下圖所示,執行器將小表mse_test_test做爲外表(驅動表),對於其中的每條記錄,經過大表(nbar_test)上的索引匹配相應記錄。3d

Nested loop

**Query 1 Test 2:**強制使用Hash Join,耗時1731.836ms

Nested loop join

  以下圖所示,執行器選擇一張表將其映射成散列表,再遍歷另一張表並從散列表中匹配相應記錄。 Hash joincode

**Query 1 Test 3:**強制使用Merge Join,耗時4956.768 ms

Merge join plan

  以下圖所示,執行器先分別對mse_test_test和nbar_test按client_key排序。其中mse_test_test使用快速排序,而nbar_test使用external merge排序,以後對兩者進行Merge Join。 Merge joinblog

Query 1 總結 1 :

經過對比Query 1 Test 1Query 1 Test 2Query 1 Test 3能夠看出Nested Loop適用於結果集很小(通常要求小於一萬條),而且內表在Join字段上建有索引(這點很是很是很是重要)。排序

  • 在大表上建立聚簇索引

**Query 1 Test 4:**強制使用Merge Join,耗時1660.228 ms

Merge join

  以下圖所示,執行器經過聚簇索引對大表(nbar_test)排序,直接經過快排對無索引的小表(mse_test_test)排序,以後對二才進行Merge Join。 Merge join

Query 1 總結 2:

經過對比Query 1 Test 3Query 1 Test 4能夠看出,Merge Join的主要開銷是排序開銷,若是能經過創建聚簇索引(若是Query必須顯示排序),能夠極大提升Merge Join的性能。從這兩個實驗能夠看出,建立聚簇索引後,查詢時間從4956.768 ms縮減到了1815.238 ms。

  • 在兩表上同時建立聚簇索引

**Query 1 Test 5:**強制使用Merge Join,耗時2575.498 ms。

Merge join with cluster index

  以下圖所示,執行器經過聚簇索引對大表(nbar_test)和小表(mse_test_test)排序,以後才進行Merge Join。 Merge join

Query 1 總結 3:

對比Query 1 Test 4Query 1 Test 5,能夠看出兩者惟一的不一樣在於對小表(mse_test_test)的訪問方式不一樣,前者使用快排,後者由於聚簇索引的存在而使用Index Only Scan,在表數據量比較小的狀況下前者比後者效率更高。由此可看出若是經過索引排序再查找相應的記錄比直接在原記錄上排序效率還低,則直接在原記錄上排序後Merge Join效率更高。

  • 刪除nbar_test上的索引

    **Query 1 Test 6:**強制使用Hash Join,耗時1815.238 ms

    時間與Query 1 Test 2幾乎相等。 Hash join without index

    以下圖所示,與Query 1 Test 2相同,執行器選擇一張表將其映射成散列表,再遍歷另一張表並從散列表中匹配相應記錄。 Hash join

Query 1 總結 4 :

經過對比Query 1 Test 2Query 1 Test 6能夠看出Hash Join不要求表在Join字段上創建索引。

兩大表Join

mse_test約100萬條記錄,nbar_test約165萬條記錄

###**Query 2:**不等值Join

select 
    count(*)
   from 
    mse_test, 
    nbar_test 
   where 
    mse_test.client_key = nbar_test.client_key
and
    mse_test.client_key between 100000 and 300000;

**Query 2 Test 1:**強制使用Hash Join,失敗

本次實驗經過設置enable_hashjoin=trueenable_nestloop=falseenable_mergejoin=false來試圖強制使用Hash Join,可是失敗了。 Nested loop

相關文章
相關標籤/搜索