Mysql 优化器内部JOIN算法hash join On-Disk Hash Join Grace Hash Join Hybrid hash join过程详解
Mysql 各種hash join算法講解
hash join的概述
提到hash join之前自然得說Nest loop join,以兩個表的關聯為例,它其實是個雙層循環,先遍歷外層的表(n條),再拿每次對應的值去匹配、循環遍歷內部的表(M條)。這樣顯然會有M*n的計算復雜度。如果能將外部表先裝載到內存,然后再做內部表的匹配、遍歷,計算的復雜度就會大大降低,這就是hash join的思想。
本文繼續介紹hash join的其它幾個算法On-Disk Hash Join、Grace Hash Join、Hybrid hash join。其中In Memory?Join classic hash join的介紹見:
Mysql 優化器內部JOIN算法hash join Nestloopjoin及classic hash join CHJ過程詳解_數據科學匯集-CSDN博客Mysql hash join之classic hash join CHJ過程詳解hash join的歷史優化器里的hash join算法在SQL Server、Oracle、postgress等數據庫早已實現,而Mysql在8.0.18之后才支持。在8.0.18之前mysql只支持嵌套循環關聯(nested loop join),這其中最簡單就是簡易嵌套循環關聯simple nestloop join,隨后mysql做了改進進而支持block nestloop join, index nestlohttps://shenliang.blog.csdn.net/article/details/120498088
On-Disk Hash Join
CHJ算法在構建表時的內存大小可通過參數join_buffer_size來設置,但因為需要把整個表都裝到內存里,所以這種方法遇到比較大的表時就顯得捉襟見肘。不過也不是沒有方法去規避,我們按照如下的策略方向做改進(分批執行的思想,記作):
1 在構建哈希表時讀取盡量多的內容到內存。
2 運行探測過程時讀取整個探測的輸入(即對應1里的內容)。
3 擦除(清空)內存里的哈希表。
4 循環步驟1里執行直至遍歷完構建過程里的數據。
不過因為需要多次讀取構建的輸入(即構建表)還是有風險且有成本的,假設構建表被分成了n份,那么在匹配探測表時將會掃描n次。這時借助磁盤分區執行的方法on-Disk Hash Join就應運而生了。詳細步驟見下:
Step1:把構建表和探測表在磁盤上分成若干小區(塊),這里分配的原則是每個小區(塊)能全部裝載到內存里,而構建表的確定和classic hash join算法類似(一般優先選擇小表)。
Step2:當所有的記錄都已經分區(塊)到磁盤上的小文件后,開始遍歷分區(塊),進入構建階段即將第一個分區通過函數函數裝載到內存里形成哈希表,然后在探測階段掃描第一個分區(塊),該分區(塊)對應構建階段里分區。因為在生成分區信息時構建表和探測表用相同的哈希函數,所以在探測階段進行匹配時很容易找到對應的分區信息。
Step3:當第一個分區(塊)處理完了之后清空內存里的哈希表,然后把第二個分區文件裝載到構建表,在探測過程用第二個分區的信息匹配探測表,依次類推直至所有的分區(塊)都遍歷完。
通過對On-Disk Hash Join的執行過程進行分析,我們不難發現該算法會在構建過程讀取IO兩次、探測過程寫一次IO。這與開頭里介紹的hash join改善算法(n次IO掃描探測表)已經有較大的進步。
構建表分區 探測表分區 構建探測兩過程??????注:
1 in-memory hash join和on-disk hash join算法都是用的msxxHash64作為哈希函數,它是在提供高質量的哈希值的同時又能保證快速(減少哈希沖突的數量)。
2 如果有一個數據集傾斜的結果集會導致構建表里的一個分區(塊)不能填入內存,那么hash join算法會按照如下的邏輯處理:
1 讀取盡量多的分區(塊)到內存。
2 在整個探測階段讀取探測分區。
3 清空內存里的哈希表。
4 調轉到步驟1直至構建分區里沒有數據。
Grace Hash Join
當連接緩存大小(join buffer size)不足時,mysql會先分片再按照classic hash join的方式處理(見On-Disk Hash Join里的注2)。但在極端情況,如果數據分布不均勻,有大量數據經過哈希后分布到一個桶內,這將導致分片后的連接緩存大小仍然不足。碰到這種情況oracle的grace hash join算法則會繼續拆分直至有足夠的內存可以存放哈希表,當然如果在關聯條件相同的情況下不論再怎么哈希還是不能拆分時grace hash join也退化為和像mysql一樣先分片再classic hash join的方式處理方式。
Hybrid hash join
如果緩存足夠多的分片數據會盡量緩存,那么就不必像GraceHash那樣將所有分片都先讀進內存,再寫到外存,最后再讀進內存進行build過程,這就是Grace Hash Join算法的核心,即在內存相對于分片比較充裕的情況下可以減少磁盤的讀寫IO。目前Oceanbase的HashJoin采用的是這種join方式。
hash join的偽代碼
result = [] join_buffer = [] partitions = 0 on_disk = False for country_row in country:if country_row.Continent == 'Asia':hash = xxHash64(country_row.Code)if not on_disk:join_buffer.append(hash)if is_full(join_buffer):# Create partitions on diskon_disk = Truepartitions = write_buffer_to_disk(join_buffer)join_buffer = []elsewrite_hash_to_disk(hash)if not on_disk:for city_row in city:hash = xxHash64(city_row.CountryCode)if hash in join_buffer:country_row = get_row(hash)city_row = get_row(hash)result.append(join_rows(country_row, city_row))else:for city_row in city:hash = xxHash64(city_row.CountryCode)write_hash_to_disk(hash) for partition in range(partitions):join_buffer = load_build_from_disk(partition)for hash in load_hash_from_disk(partition):if hash in join_buffer:country_row = get_row(hash)city_row = get_row(hash)result.append(join_rows(country_row, city_row)) join_buffer = []代碼簡介
從country表里讀取每一行并計算code字段對應的哈希值并存儲在連接緩存內(join buffer)。如果連接緩存滿了則走on-disk算法并哈希后的緩存寫到外部磁盤。也正是在此時分區的個數確定,然后對country表剩下的數據繼續哈希。
?對于in-memory的算法只需要通過循環比較city和緩存里的哈希值即可,而對于on-disk算法哈希的city表會被首先計算并存放在磁盤然后再通過分區一個個的匹配。
參考:
MySQL :: WL#2241: Hash join
New features of MySQL 8.0 hash join | Develop Paper
總結
以上是生活随笔為你收集整理的Mysql 优化器内部JOIN算法hash join On-Disk Hash Join Grace Hash Join Hybrid hash join过程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 技嘉电脑怎么用u盘启动不了 技嘉电脑无法
- 下一篇: SQL Server 堆heap 非聚集