MySQL Hash Join前世今生

来源:https://www.cnblogs.com/greatsql/archive/2022/11/21/16913796.html
-Advertisement-
Play Games

GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者:nw MySQL Hash Join前世今生 因工作需要,對MySQL Hash Join的內部實現做了一些探索和實踐,對這個由8.0.18開始引 ...


  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。
  • GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。
  • 作者:nw

MySQL Hash Join前世今生

因工作需要,對MySQL Hash Join的內部實現做了一些探索和實踐,對這個由8.0.18開始引入的連接演算法有了一定的瞭解,寫文總結與各位大佬分享,歡迎大家指教。因篇幅較長,這份總結分成若幹部分,我們今天先一起來看一下MySQL Hash join的變遷史。

爬了一下 MySQL worklog[1],並結合源碼及各版本的實際使用,個人認為比較重要的worklogs為如下幾個, 其它的變更一般圍繞這些worklogs做的小調整或bugfixes,這裡我們就不一一列舉。

1. WL#2241: Hash join (變更版本:8.0.18)

主要內容:

  • 新增執行類HashJoinIterator,實現hash join演算法原型 (支持on-disk hash)
  • HASH JOIN 僅支持INNER JOIN,並對使用hash join做了限制:關聯表連接條件必須至少包含一條等值條件(equi-join condition, 如t1.a = t2.a),且join條件中的列不包含索引

註:這裡我認為官網的 Release Notes[2] 描述是不太準確的,以如下例子為例,雖然該查詢包含了非等值連接條件(non-equi-join condition, 如 t1.a <> t2.a ,t1.a = 1, t2.a > 1 等),但實際查詢中還是使用hash join的, 因為查詢語句在解析執行過程中,可能會經歷語句重寫、sql優化等步驟,與表象上會有所不同,建議藉助explain工具,查看實際上查詢語句選擇的join演算法。

-- 版本:8.0.18
-- 在創建iterator時,t1.a > 1 會被當成表的過濾條件,而非inner join的join條件
-- 此查詢仍使用了hash join(join 條件為空)
EXPLAIN FORMAT=tree SELECT * FROM t1 JOIN t2 ON t1.i > 1;
-> Inner hash join  (cost=1.17 rows=3)
    -> Table scan on t2  (cost=0.34 rows=2)
    -> Hash
        -> Filter: (t1.i > 1)  (cost=0.65 rows=1)
            -> Table scan on t1  (cost=0.65 rows=4)
  • (儘量)使用HASH JOIN演算法替換BNL(Blocked Nested-Loop)連接演算法

2. WL#13377: Add support for hash outer, anti and semi join( 變更版本:8.0.20)

主要內容:

  • HASH INNER JOIN改進

    INNER JOIN中的non-equi-join conditions, 會被附為hash join的過濾條件:
-- 版本:8.0.20
EXPLAIN FORMAT=tree SELECT * FROM t1 JOIN t2 ON t1.i <> t2.i;
-> Filter: (t1.i <> t2.i)  (cost=1.10 rows=2)
    -> Inner hash join (no condition)  (cost=1.10 rows=2)
        -> Table scan on t2  (cost=0.18 rows=2)
        -> Hash
            -> Table scan on t1  (cost=0.45 rows=2)
  • HAH JOIN支持outer join/anti join/semi join
-- 版本:8.0.20
-- Left outer join
EXPLAIN FORMAT=tree SELECT * FROM t1 LEFT JOIN t2 ON t1.i=t2.i;
-> Left hash join (t2.i = t1.i)  (cost=0.88 rows=4)                                                                                                                                                                          
    -> Table scan on t1  (cost=0.45 rows=2)                                                                                                                                                                                    
    -> Hash                                                                                                                                                                                                                    
        -> Table scan on t2  (cost=0.23 rows=2)

-- Right outer join(註:MySQL在parser階段會將所有的right join改寫為left join
--                      所以我們這裡看到的explain為Left hash join
EXPLAIN FORMAT=tree SELECT * FROM t1 RIGHT JOIN t2 ON t1.i=t2.i;
-> Left hash join (t1.i = t2.i)  (cost=0.88 rows=4)                                                                                                                                                                          
    -> Table scan on t2  (cost=0.45 rows=2)                                                                                                                                                                                    
    -> Hash                                                                                                                                                                                                                    
        -> Table scan on t1  (cost=0.23 rows=2)

-- Semijoin
EXPLAIN FORMAT=tree SELECT * FROM t1 WHERE (t1.i) IN (SELECT t2.i FROM t2);
-> Hash semijoin (t2.i = t1.i)  (cost=0.83 rows=2)
    -> Table scan on t1  (cost=0.45 rows=2)
    -> Hash
        -> Table scan on t2  (cost=0.18 rows=2)

-- Antijoin
EXPLAIN FORMAT=tree 
SELECT * FROM t2 WHERE NOT EXISTS (SELECT * FROM t1 WHERE t1.i = t2.i);
-> Hash antijoin (t1.i = t2.i)  (cost=1.10 rows=4)                                                                                                                                                                           
    -> Table scan on t2  (cost=0.45 rows=2)                                                                                                                                                                                    
    -> Hash                                                                                                                                                                                                                    
        -> Table scan on t1  (cost=0.45 rows=2)
  • 所有使用BNL的連接,都將被轉為使用HASH JOIN
  • non-equi-join conditions 處理

    Hash join iterator引入"extra" condition的概念,部分 non-equi-join conditions會被當成Hash join iteratorextra condition, 在建hash table時,join key的計算不依賴這些條件,但會在hash查找到匹配項後,作為附加的過濾條件:
-- 版本: 8.0.20
-- 註: t1.i > 1 會被放到hash join的 extra conditions中
EXPLAIN FORMAT=tree SELECT * FROM  t1 LEFT JOIN t2 ON t1.i=t2.i AND t1.i > 1;
-> Left hash join (t2.i = t1.i), extra conditions: (t1.i > 1)  (cost=0.88 rows=4)
    -> Table scan on t1  (cost=0.45 rows=2)
    -> Hash
        -> Table scan on t2  (cost=0.23 rows=2)

相關源碼:

// 代碼版本:8.0.20 HEAD: commit 91a17cedb1ee880fe7915fb14cfd74c04e8d6588
// 文件名:sql/hash_join_iterator.cc
int HashJoinIterator::ReadNextJoinedRowFromHashTable() {
  int res;
  bool passes_extra_conditions = false;
  do { // 所有匹配行都需要多做一個extra condition的判定,因為有可能存在不同行的記錄
       // 映射在同一個join key的情況,因此需要通過遍歷逐條讀取出符合extra conditions
       // 的數據
    res = ReadJoinedRow();  // 讀取通過join key查找已經得到的匹配行(單行記錄)
    DBUG_ASSERT(res == 0 || res == -1);
    
    if (res == 0) {
      passes_extra_conditions = JoinedRowPassesExtraConditions();
      if (thd()->is_error()) {
        // Evaluation of extra conditions raised an error, so abort the join.
        return 1;
      }

      if (!passes_extra_conditions) {
        ++m_hash_map_iterator;
      }
    }
  } while (res == 0 && !passes_extra_conditions);
}

3. WL#13459: Optimize hash table in hash join (變更版本:8.0.23)

主要內容:

  • 優化hash join table的創建方法

    這裡MySQL所說的“優化”, 實際上會更激進一點,這個版本中,MySQL直接使用了一個基於 robin hood hashing[3] 實現的 開源hash table[4] ,更換了原先的hash join table實現( from mem_root_unordered_multimap to robin_hood::unordered_flat_map)

  • 優化記憶體管理和使用,降低了 on-disk hash 的頻率,提高了記憶體有效使用率等(其他的改進內容及提升的效果可以參考MySQL 8.0.23的release notes[5] 以及 worklog #13459[6] 的Low Level Design頁面)

本篇我們對MySQL hash join的3個重要變更做了簡要的總結,算是對它的前世今生有了印象,謝謝各位閱讀;之後讓我們會結合具體的sql查詢樣例,去跟蹤一下對應的代碼執行流程,不日更新,敬請期待。

參考文檔

[1] MySQL worklog: https://dev.mysql.com/worklog/

[2] MySQL 8.0.18 release notes#optimizer: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-18.html#mysqld-8-0-18-optimizer

[3] robin hood hashing 演算法介紹: https://programming.guide/robin-hood-hashing.html

[4] robin hood hasing 開源實現: https://github.com/martinus/robin-hood-hashing

[5] MySQL 8.0.23 release notes#optimizer: https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-23.html#mysqld-8-0-23-optimizer

[6] MySQL worklog #13459: https://dev.mysql.com/worklog/task/?id=13459


Enjoy GreatSQL

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • JZ79 判斷是不是平衡二叉樹 描述 輸入一棵節點數為 n 二叉樹,判斷該二叉樹是否是平衡二叉樹。 在這裡,我們只需要考慮其平衡性,不需要考慮其是不是排序二叉樹 平衡二叉樹(Balanced Binary Tree),具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個 ...
  • [C# 中的序列化與反序列化](.NET 源碼學習) 關鍵詞:序列化(概念與分析) 三種序列化(底層原理 源碼) Stream(底層原理 源碼) 反射(底層原理 源碼) 假如有一天我們要在在淘寶上買桌子,桌子這種很不規則不東西,該怎麼從一個城市運輸到另一個城市,這時候一般都會把它拆掉成板子,再裝到箱 ...
  • 摘要 這片文章主要是記錄自己的整活過程,涉及到的技術包括.NET IoT, .NET Web, .NET MAUI,框架採用的也是最新的.NET 7。 本人是用的樹莓派Zero 2 W(ubuntu-22.04)進行開發測試,但是.NET IoT庫也有社區張高興提交的香橙派GPIO引腳的映射,香橙派 ...
  • 創建消息提示控制項 internal class Message : ContentControl { public int Time { get; set; } [Bindable(true)] public MessageType MessageType { get { return (Messa ...
  • 本文閱讀目錄 1. Avalonia UI簡介 Avalonia UI文檔教程:https://docs.avaloniaui.net/docs/getting-started 隨著跨平臺越來越流行,.NET支持跨平臺至今也有十幾年的光景了(Mono開始)。 但是目前基於.NET的跨平臺,大多數還是 ...
  • 代碼生成器(CodeBuilder) 經過這幾個版本的完善,目前功能也趨於穩定,詳細的線上文檔也得到維護,不失為一款強大的代碼生成工具。 官網:http://www.fireasy.cn/codebuilder ==版本維護== Version 2.9.41、解決擴展文件編輯與編譯有問題;2、提升應 ...
  • 個人名片: 對人間的熱愛與歌頌,可抵歲月冗長:sun_with_face: Github👨🏻‍💻:念舒_C.ying CSDN主頁✏️:念舒_C.ying 個人博客:earth_asia: :念舒_C.ying 1 基礎環境 創建centos虛擬機,需要兩張網路適配器 1.1 配置網卡 IP地 ...
  • (文章目錄) 一、調度演算法的原理和分類 1.進程調度簡介 進程調度的研究是整個操作系統理論的核心,在多進程的操作系統中,進程調度是一個全局性的、關鍵性的問題,它對系統的總體設計、系統的實現、功能設置以及各方面的性能都有著決定性的影響。進程運行需要各種各樣的系統資源,如記憶體、文件、印表機和最寶貴的CP ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...