目錄
MySQL8.0正式引入了Hash Join的連接方式,下面介紹一下這種連接方式,並且和之前的連接方式做一下對比。
準備工作一:驅動表和被驅動表
比如這樣一個使用了連接的sql:
SELECT * from tableA join tableB on tableA.code=tableB.code;
tableA和tableB通過code字段進行關聯,一般情況下,MySQL的查詢優化器會先選擇其中一個表作爲驅動表,假設選擇了表A,那麼表A是驅動表,驅動表的作用就是,遍歷驅動表的每一條記錄,對於某條記錄中的code,從另外一個表B中查詢對應的記錄,然後把兩條記錄做關聯,那麼表B就是被驅動表。
實際情況中查詢優化器會按照自己的邏輯選擇驅動表和被驅動表。
準備工作二:MySQL8.0之前的連接方式
在MySQL8.0引入Hash Join之前,使用的是經典的Nested Loops Join(NLJ)算法,嵌套循環算法。
Nested Loops Join有以下幾種演進版本:
1,Simple Nested Loops Join(SNLJ),簡單嵌套循環算法
連接雙方在連接字段上都沒有索引,所以此算法沒有什麼套路,從驅動表取出記錄,按照join的條件在被驅動表中查詢,找到可連接的記錄,把數據連接並返回。
時間複雜度:O(N2)
顯然效率不是很高。
查詢優化器考慮選擇記錄少的表作爲驅動表。
2,Index Nested Loops Join(INLJ),索引嵌套循環連接
有一方在連接字段上有索引,這種場景在MySQL的使用中見的比較多。
優化器會考慮選擇有索引的一方作爲被驅動表,雙方都有索引則選擇索引高度低的,索引高度一樣則選擇記錄數多的作爲被驅動表,對於驅動表的每一條記錄,在被驅動表中使用索引查詢,大大減少了比較次數,提高了查詢效率。
索引是主鍵時效率更高。
3,Block Nested Loops Join(BNLJ),塊嵌套循環連接
可能人們覺得索引嵌套循環連接的效率還可以再優化一下,出發點在於每次試圖匹配被驅動表的數據時,都要把被驅動表加載到內存,所以,爲了減少被驅動表的加載次數,爲了每次加載被驅動表時可以儘量多的匹配驅動表數據,塊嵌套循環連接出現了。
原理:
塊嵌套循環連接設定了一塊緩存區域(Join Buffer),存放驅動表相關數據,包括查詢結果要用的字段,查詢條件中用到的字段,和連接被驅動表的字段,如果此緩存區域足夠大,能夠包括驅動表所有數據,那麼查詢時驅動表和被驅動表都只需要掃描一次,就能得到最終的匹配結果,極大提高了查詢效率。
塊嵌套循環連接的原理也告訴我們,平時儘量不要用select *這種查詢,可以提高緩衝區的使用效率。
緩衝區大小可以使用系統變量Join_buffer_size來調整。
優化:Regular join buffer和Incremental join buffer
在此基礎上,MySQL對這套邏輯又做了一點優化,把緩存區域Join Buffer分成兩種,原來邏輯下的緩衝區叫Regular join buffer,又加了一種增量緩衝區Incremental join buffer。
舉個例子:當sql中有三個表相互關聯時,比如表A表B表C三表關聯,那麼MySQL會使用兩個緩衝區,第一個緩衝區存放表A的相關字段(用來和表B關聯),第二個緩衝區存放表A和表B完成關聯後的相關字段(用來和表C關聯),那麼第二個緩衝區實際存放的信息將會是表B的相關字段,加上一個指針,指向第一個緩衝區中表A的對應字段。在此場景下,第一個緩衝區就是Regular join buffer,第二個緩衝區屬於Incremental join buffer。
這種優化方式提高了緩衝區內存的利用率,從而提高了查詢效率。
塊嵌套循環連接的原理和Hash Join就很像了。
4,Batched key Access Join,批量鍵訪問連接
批量鍵訪問連接基於MySQL5.6的一種新特性:MRR(multi range read)。當被驅動表的鏈接字段有非主鍵索引時,而是通過範圍掃描讀取一部分記錄放入內存中,然後按照主鍵排序,這樣匹配到數據後需要按對應的主鍵索引去查詢被驅動表的真實數據時,可以按照排好序的主鍵進行順序訪問,因爲InnoDB葉子節點的數據也是按主鍵排序的,所以這種讀取方式能提高查詢效率。
要打開MRR,需要打開系統變量optimizer_switch中的mrr和mrr_cost_based選項。
MRR的使用流程中用到了排序,有一定的開銷,有些sql中效率可能沒有那麼高。
Hash Join
終於來到了Hash Join,Hash Join可以在被驅動表沒有索引的情況下進行快速的連接並查詢。
原理:
1,Hash Join首先使用了Join Buffer,把驅動表相關字段存入內存。這一步和塊嵌套循環連接套路相同。
2,把Join Buffer中對應的字段值生成一個散列表,保存在內存中。這一步叫build。
3,掃描被驅動表,對被驅動表中的相關字段進行散列並比較。這一步叫probe。
可見,Hash Join也依賴Join Buffer,在最好的場景下,如果Join Buffer能覆蓋驅動表所有相關字段,那麼在查詢的過程中驅動表和被驅動表都只需要掃描一次,如果散列算法夠好,比較次數也只是被驅動表的記錄數。
Hash Join只能用於等值連接。
大表連接Hash Join的優化效果比較明顯。
要使用Hash Join,需要把系統變量join_cache_level設爲大於等於4的值,並且把optimizer_switch的join_cache_hashed設爲on。
總結:
1,驅動表數據量應該儘量小。
2,被驅動表查詢效率儘量高,比如可以建立索引。
3,正確的配置相關參數,可以極大的提高查詢效率。
完
MySQL8.0全部學習筆記:
MySQL8.0新特性學習筆記(一):binlog複製策略優化