如何讓 JOIN 跑得更快?

JOIN 一直是數據庫性能優化的老大難問題,本來挺快的查詢,一旦涉及了幾個 JOIN,性能就會陡降。而且,參與 JOIN 的表越大越多,性能就越難提上來。

其實,讓 JOIN 跑得快的關鍵是要對 JOIN 分類,分類之后,就能利用各種類型 JOIN 的特征來做性能優化了。

JOIN 分類

有 SQL 開發經驗的同學都知道,絕大多數 JOIN 都是等值 JOIN,也就是關聯條件為等式的 JOIN。非等值 JOIN 要少見得多,而且多數情況也可以轉換成等值 JOIN 來處理,所以我們可以只討論等值 JOIN。

等值 JOIN 主要又可以分為兩大類:外鍵關聯和主鍵關聯。

外鍵關聯是指用一個表的非主鍵字段,去關聯另一個表的主鍵,前者稱為事實表,后者為維表。比如下圖中,訂單表是事實表,客戶表、產品表、雇員表是維表。

在這里插入圖片描述

外鍵表是多對一關系,而且是不對稱的,事實表和維表的位置不能互換。需要說明的是,這里說的主鍵是指邏輯上的主鍵,也就是在表中取值唯一、可以用于唯一確定某條記錄的字段(或字段組),不一定在數據庫表上建立過主鍵。

主鍵關聯是指用一個表的主鍵關聯另一個表的主鍵或部分主鍵。比如下圖中客戶和 VIP 客戶、訂單表和訂單明細表的關聯。

在這里插入圖片描述

客戶和 VIP 客戶按照主鍵關聯,這兩個表互為同維表。訂單則是用主鍵去關聯明細的部分主鍵,我們稱訂單表是主表,明細表是子表。

同維表是一對一關系。且同維表之間是對稱的,兩個表的地位相同。主子表則是一對多關系,而且是不對稱的,有明確的方向。仔細觀察會發現,這兩類 JOIN 都涉及到主鍵了。

而不涉及主鍵的 JOIN 會導致多對多關系,大多數情況都沒有業務意義。換句話說,上述這兩大類 JOIN 涵蓋了幾乎全部有業務意義的 JOIN。

如果我們能利用 JOIN 總會涉及主鍵這個特征做性能優化,能解決掉這兩大類 JOIN,其實也就意味著解決了大部分 JOIN 性能問題。

但是,SQL 對 JOIN 的定義并不涉及主鍵,只是兩個表做笛卡爾積后再按某種條件過濾。這個定義很簡單也很寬泛,幾乎可以描述一切。

但是,如果嚴格按這個定義去實現 JOIN,也就沒辦法在性能優化時利用主鍵的特征了。SPL 改變了 JOIN 的定義,專門針對這兩大類 JOIN 分別處理,利用了主鍵的特征減少運算量,從而實現性能優化的目標。

下面我們來看看 SPL 具體是怎么做的。

外鍵關聯

如果事實表和維表都不太大,可以全部裝入內存,SPL 提供了外鍵地址化方法:先把事實表中的外鍵字段值轉換為對應維表記錄的地址,之后引用維表字段時,就可以用地址直接取出了。

以前面的訂單表、雇員表為例,假定這兩個表已經被讀入內存。外鍵地址化的工作機制是這樣的:對于訂單表某記錄 r 的 eid 字段,到雇員表中找到這個 eid 字段值對應的記錄,得到其內存地址 a,再將 r 的 eid 字段值替換成 a。對訂單表的所有記錄都做好這樣的轉換,就完成了外鍵地址化。

這時候,訂單表記錄 r 要引用雇員表字段時,直接用 eid 字段存儲的地址 a 取出雇員表記錄和字段就可以了,相當于常數時間內就能取得雇員表的字段,不需要再到雇員表做查找。可以在系統啟動時把事實表和維表讀入內存,并一次性做好外鍵地址化,即預關聯。

這樣,在后續關聯計算時就能直接用事實表外鍵字段中的地址去取維表記錄,完成高性能的 JOIN 計算。外鍵地址化和預關聯的詳細原理請參考:http://c.raqsoft.com.cn/article/1616970721547

SQL 通常使用 HASH 算法來做內存連接,需要計算 HASH 值和比對,性能會比直接用地址讀取差很多。

SPL 之所以能實現外鍵地址化,是利用了維表的關聯字段是主鍵這一特征。上面例子中,關聯字段 eid 是雇員表的主鍵,具有唯一性。

訂單表中的每個 eid 只會唯一對應一條雇員記錄,所以才能把每個 eid 轉換成它唯一對應的那條雇員記錄的地址。而 SQL 對 JOIN 的定義中沒有主鍵的約定,就不能認定與事實表中外鍵關聯的維表記錄有唯一性,有可能發生與多條記錄關聯的情況。

對于訂單表的記錄來講,eid 值沒有辦法唯一對應一條雇員記錄,就無法做到外鍵地址化了。而且 SQL 也沒有記錄地址這種數據類型,結果會導致每次關聯時還是要計算 HASH 值并比對。

只是兩個表 JOIN 時,外鍵地址化和 HASH 關聯的差別還不是非常明顯。這是因為 JOIN 并不是最終目的,JOIN 之后還會有其它很多運算,JOIN 本身運算消耗時間的占比相對不大。但事實表常常會有多個維表,甚至維表還會有很多層。比如訂單關聯產品,產品關聯供應商,供應商關聯城市,城市關聯國家等等。在關聯表很多時,外鍵地址化的性能優勢會更明顯。

下面的測試,在關聯表個數不同的情況下對比 SPL 與 Oracle 的性能差異,可以看出在表很多時,外鍵地址化的優勢相當明顯:

測試的詳細情況請參考:性能優化技巧:預關聯。

對于只有維表能裝入內存,而事實表很大需要外存的情況,SPL 提供了外鍵序號化方法:預先將事實表中的外鍵字段值轉換為維表對應記錄的序號。

關聯計算時,分批讀入新事實表記錄,再用序號取出對應維表記錄。

以上述訂單表、產品表為例,假定產品表已經裝入內存,訂單表存儲在外存中。外鍵序號化的過程是這樣:先讀入一批訂單數據,設其中某記錄 r 中的 pid 對應的是內存中產品表的第 i 條記錄。

我們要將 r 中的 pid 字段值轉換為 i。對這批訂單記錄都完成這樣的轉換后,再做關聯計算時,從外存中分批讀入訂單數據。

對于其中的記錄 r,就可以直接根據 pid 值,去內存中的產品表里用位置取出相應的記錄,也避免了查找動作。

外鍵序號化原理更詳細的介紹參考:http://c.raqsoft.com.cn/article/1617144101332

數據庫通常會把小表讀入內存,再分批讀入大表數據,用哈希算法做內存連接,需要計算哈希值和比對。而 SPL 使用序號定位是直接讀取,不需要進行任何比對,性能優勢比較明顯。雖然預先把事實表的外鍵字段轉換成序號需要一定成本,但這個預計算只需要做一次,而且可以在多次外鍵關聯中得到復用。SPL 外鍵序號化同樣利用了維表關聯字段是主鍵的特征。

如前所述,SQL 對 JOIN 的定義沒有主鍵的約定,無法利用這一特征做到外鍵序號化。另外,SQL 使用無序集合的概念,即使我們事先把外鍵序號化了,數據庫也無法利用這個特點,不能在無序集合上使用序號快速定位的機制,最快也就是用索引查找。

而且,數據庫并不知道外鍵被序號化了,仍然會去計算 HASH 值和比對。下面這個測試,在不同并行數情況下,對比 SPL 和 Oracle 完成大事實表、小維表關聯計算的速度,SPL 跑的比 Oracle 快 3 到 8 倍。

測試結果見下圖:

這個測試更詳細的信息請參考:性能優化技巧:外鍵序號化。

如果維表很大也需要外存,而事實表較小能裝入內存,SPL 則提供了大維表查找機制。

如果維表和事實表都很大,SPL 則使用單邊分堆算法。對于維表過濾后再關聯的情況,SPL 提供了索引復用方法及對位序列等方法。

數據量大到需要分布式計算時,如果維表較小,SPL 采用復寫維表機制,將維表在集群節點上復制多份;如果維表很大,則采用集群維表方法以保證隨機訪問。這兩種方法都可以有效的避免 Shuffle 動作。相比而言,SQL 體系下不能區分出維表,HASH 拆分方法要將兩個表都做 Shuffle 動作,網絡傳輸量要大得多。

主鍵關聯

主鍵關聯涉及的表一般都比較大,需要存儲在外存中。SPL 為此提供了有序歸并方法:預先將外存表按照主鍵有序存儲,關聯時順序取出數據做歸并計算。

以客戶和 VIP 客戶兩個表做內連接為例,假設已經預先將兩個表按照主鍵 cid 有序存儲在外存中。關聯時,從兩個表的游標中讀取記錄,逐條比較 cid 值。如果 cid 相等,則將兩表的記錄合并成結果游標的一條記錄返回。如果不相等,則 cid 小的那個游標再讀取記錄,繼續判斷。重復這些動作直到任何一個表的數據被取完,返回的游標就是 JOIN 的結果。

對于兩個大表關聯,數據庫通常使用哈希分堆算法,復雜度是乘法級的。而有序歸并算法復雜度是加法級,性能會好很多。而且,數據庫做大數據的外存運算時,哈希分堆會產生緩存文件的讀寫動作。有序歸并算法則只需要對兩個表依次遍歷,不必借助外存緩存,可以大幅降低 IO 量,有巨大的性能優勢。

預先按照主鍵排序的成本雖高,但是一次性做好即可,以后就總能使用歸并算法實現 JOIN,性能可以提高很多。同時,SPL 也提供了在有追加數據時仍然保持數據整體有序的方案。

這類 JOIN 的特征在于關聯字段是主鍵或部分主鍵,有序歸并算法正是根據這個特征來設計的。因為不管是同維表還是主子表,關聯字段都不會是主鍵之外的其他字段,所以我們將關聯表按照主鍵有序這一種方式排序存儲就可以了,不會出現冗余。而外鍵關聯就不具備這個特征,不能使用有序歸并。具體來說,是因為事實表的關聯字段不是主鍵,會存在多個要參與關聯的外鍵字段,我們不可能讓同一個事實表同時按多個字段都有序。

SQL 對 JOIN 的定義不區分 JOIN 類型,不假定某些 JOIN 總是針對主鍵的,就沒辦法從算法層面上利用主鍵關聯的特征。而且,前面說過 SQL 基于無序集合概念,數據庫不會刻意保證數據的物理有序性,很難實施有序歸并算法。

有序歸并算法的優勢還在于易于分段并行。以訂單和訂單明細按 oid 關聯為例,假如將兩表都按照記錄數大致平均分為 4 段,訂單第 2 段的 oid 有可能會出現在明細第 3 段,類似的錯位會導致錯誤的計算結果。SPL 再次利用主鍵 oid 的有序性,提供同步分段機制,解決了這個問題:先將有序的訂單表分為 4 段,再找到每一段起止記錄的 oid 值形成 4 個區間,將明細表也分成同步的 4 段。這樣,在并行計算時兩表對應分段就不會出現錯位了。由于明細表也對 oid 有序,可以迅速地按照起止 oid 定位,不會降低有序歸并的性能。

有序歸并和同步分段并行的原理,詳見:SPL 有序歸并關聯 http://c.raqsoft.com.cn/article/1647665012651

傳統的 HASH 分堆技術實現并行就比較困難了,多線程做 HASH 分堆時需要同時向某個分堆寫出數據,造成共享資源沖突;而下一步實現某組分堆關聯時又會消費大量內存,無法實施較大的并行數量。

實際測試證明,在相同情況下,我們對兩個大表做主鍵關聯測試(詳情參見性能優化技巧:有序歸并),結果是 SPL 比 Oracle 快了近 3 倍:

除了有序歸并,SPL 還提供了很多高性能算法,全面提高主鍵關聯 JOIN 的計算速度。包括:附表機制,可以將多表一體化存儲,減少存儲數據量的同時,還相當于預先完成了關聯,不需要再比對了;關聯定位算法,實現先過濾再關聯,可以避免全表遍歷,獲得更好的性能等等。

當數據量繼續增加,需要多臺服務器集群時,SPL 提供復組表機制,將需要關聯的大表按照主鍵分布到集群節點上。相同主鍵的數據在同一節點,避免分機之間的數據傳輸,也不會出現 Shuffle 動作。

回顧與總結

回顧上面兩大類、各場景 JOIN,采用 SPL 分情況提供的高性能算法,可以利用不同類型 JOIN 的特征提速,讓 JOIN 跑得更快。SQL 對上述這么多種 JOIN 場景籠統的處理,就沒辦法針對不同 JOIN 的特征來實施這些高性能算法。比如:事實表和維表都裝入內存時,SQL 只能按照鍵值計算 HASH 和比對,無法利用地址直接對應;SQL 數據表無序,在大表按照主鍵關聯時無法做到有序歸并,只能使用 HASH 分堆,有可能會出現多次緩存的現象,性能有一定的不可控性。

并行計算方面,SQL 單表計算時還容易做到分段并行,多表關聯運算時一般就只能事先做好固定分段,很難做到同步動態分段,這就難以根據機器的負載臨時決定并行數量。

對于集群運算也是這樣,SQL 在理論上不區分維表和事實表,要實現大表 JOIN 就會不可避免地產生占用大量網絡資源的 HASH Shuffle 動作,在集群節點數太多時,網絡傳輸造成的延遲會超過節點多帶來的好處。

SPL 設計并應用了新的運算和存儲模型,可以在原理和實現上解決 SQL 的這些問題。對于 JOIN 的不同分類和場景,程序員有針對性的采取上述高性能算法,就能獲得更快的計算速度,讓 JOIN 跑得更快。

SPL下載地址:集算器 (SPL) 最新版發布啦『發布日期 20220402』 - 乾學院

SPL開源地址:https://github.com/SPLWare/esProc

SPL近年引用最高論文

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/719698.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/719698.shtml
英文地址,請注明出處:http://en.pswp.cn/news/719698.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Effective Programming 學習筆記

1 基本語句 1.1 斷言 在南溪看來,斷言可以用來有效地確定編程中當前代碼運行的前置條件,尤其是以下情況: 第三方工具庫對輸入數據的依賴,例如:minitouch庫對Android版本的要求

第三百八十一回

文章目錄 1. 概念介紹2. 修改方法 015buttonStyle.png2.1 修改形狀2.2 修改顏色2.3 修改位置 3. 示例代碼4. 內容總結 我們在上一章回中介紹了"如何創建以圖片為背景的頁面"相關的內容,本章回中將介紹如何修改按鈕的形狀.閑話休提,讓我們一起T…

2024年華為OD機試真題-文件緩存系統-Python-OD統一考試(C卷)

題目描述: 請設計一個文件緩存系統,該文件緩存系統可以指定緩存的最大值(單位為字節)。 文件緩存系統有兩種操作:存儲文件(put)和讀取文件(get) 操作命令為put fileName fileSize或者get fileName 存儲文件是把文件放入文件緩存系統中;讀取文件是從文件緩存系統中訪問已存…

06. Nginx進階-Nginx代理服務

proxy代理功能 正向代理 什么是正向代理? 正向代理(forward proxy),一個位于客戶端和原始服務器之間的服務器。 工作原理 為了從原始服務器獲取內容,客戶端向代理發送一個請求并指定目標(即原始服務器…

為不同文章形式選擇不同的WordPress文章模板

在寫文章的時候選擇不同的文章形式,然后打開文章的時候會調用不同文章形式的模板。比如,文章形式為video ,就調用single-video.php模板,其它文章形式類似,可以添加多個文章樣式。 //為不同文章形式的內容添加不同的si…

chatgpt-next-web搭建教程,超低成本部署屬于自己的ChatGPT

隨著AI的應用變廣,各類AI程序已逐漸普及,尤其是在一些日常辦公、學習等與撰寫/翻譯文稿密切相關的場景,大家都希望找到一個適合自己的穩定可靠的ChatGPT軟件來使用。 ChatGPT-Next-Web就是一個很好的選擇。它是一個Github上超人氣的免費開源…

Spring AOP在業務中常見的使用方式

目錄 1、動態代理 1.1、jdk動態代理 1.2、cglib動態代理 1.3、動態代理的好處 2、什么是AOP 2.1、AOP常用術語 2.2、切面的構成 3、使用aspectJ框架實現AOP 3.1、aspectJ簡介 聲明實現類ServiceImpl 聲明切面 3.3、AfterReturning后置通知 切面類代碼 3.4、Aroun…

2核4G云服務器租用價格_2核4G云主機優惠價格_2024年報價

租用2核4G服務器費用價格,2核4G云服務器多少錢一年?1個月費用多少?阿里云2核4G服務器30元3個月、輕量應用服務器2核4G4M帶寬165元一年、企業用戶2核4G5M帶寬199元一年;騰訊云輕量2核4G服務器5M帶寬165元一年、252元15個月、540元三…

Spring IOC在業務中常見的使用方式

目錄 1、什么是IOC 2、java實現創建對象的方式有哪些 3、基于配置文件的di實現 3.1、什么是di 3.2、入門案例 3.3、環境搭建 接口和實現類 ioc配置文件 測試程序 3.4、案例總結 3.5、簡單類型屬性的賦值(set注入) set注入要求 JavaBean sp…

前端項??件很?,?且??訪問速度慢,如何在前端側提?性能?

1. 網絡優化 減少HTTP請求的數量,可以通過合并CSS和JavaScript文件來實現。使用CDN(內容分發網絡)來加速靜態資源的加載速度。對圖片進行壓縮,選擇正確的格式,并實現懶加載技術,以減少頁面初次加載時的數據…

代碼隨想錄day12(2)字符串:重復的子字符串(leetcode459)

題目要求:給定一個非空的字符串,判斷它是否可以由它的一個子串重復多次構成。給定的字符串只含有小寫英文字母,并且長度不超過10000。 思路: 一、首先對于暴力解法,可以枚舉所有的字串進行判斷。但是枚舉時實際上只需…

rt thread stdio如何同時生成bin和hex

一、rt thread stdio默認生成bin文件: rt thread stdio 軟件編譯時,默認生成bin文件; 二、rt thread stdio如何同時生成bin和hex 右鍵單擊-->項目-->屬性-->C/C構建-->設置-->構建步驟-->(構建后步驟)命令: …

視頻如何無水印保存?這三種下載方法趕緊收藏

在互聯網時代,視頻已成為我們獲取信息、娛樂休閑的重要途徑。然而,有時我們想要保存或分享某些視頻時,卻發現下載起來卻帶有水印。為了解決這個問題,今天給大家帶來幾個無水印下載的方法。 方法一:水印云 水印云是一…

Python使用模塊和庫編程

歸納編程學習的感悟, 記錄奮斗路上的點滴, 希望能幫到一樣刻苦的你! 如有不足歡迎指正! 共同學習交流! 🌎歡迎各位→點贊 👍 收藏? 留言?📝 路在腳下,勇往直前&#x…

Spring Boot2.2.4版本啟動項目時,訪問登錄接口顯示頁面不存在

問題觸發場景:IDEA 2023.3.4 SpringBoot 2.2.4 上面4張圖片分別是項目結構、Spring Boot啟動配置、SpringMVC配置和頁面展示在項目中存放的位置,表面上看上去沒有太大問題,項目應該會達到預期結果,但是bug總是在不經意間出現&…

MySQL數據庫運維第一篇(日志與主從復制)

文章目錄 一、錯誤日志二、二進制日志三、查詢日志四、慢查詢日志(記錄超時的sql語句)五、主從復制概括六、主從復制原理七、搭建主從復制八、主從復制的測試 在這篇深入的技術文章中,作者將以明晰透徹的方式詳細介紹MySQL數據庫中關鍵的日志…

XGB-16:自定義目標和評估指標

概述 XGBoost被設計為一個可擴展的庫。通過提供自定義的訓練目標函數和相應的性能監控指標,可以擴展它。本文介紹了如何為XGBoost實現自定義的逐元評估指標和目標。 注意: 排序不能自定義 在接下來的兩個部分中,將逐步介紹如何實現平方對數…

【EAI 027】Learning Interactive Real-World Simulators

Paper Card 論文標題:Learning Interactive Real-World Simulators 論文作者:Mengjiao Yang, Yilun Du, Kamyar Ghasemipour, Jonathan Tompson, Leslie Kaelbling, Dale Schuurmans, Pieter Abbeel 作者單位:UC Berkeley, Google DeepMind, …

【 Docker 容器詳細介紹和說明】

Docker 容器詳細介紹和說明 Docker 容器詳細介紹和說明Docker 安裝步驟(以Ubuntu為例):使用Docker創建并運行容器:VSCode遠程連接Docker容器:步驟1:配置Docker環境步驟2:配置PyCharm步驟3&#…

日本發動全面侵華戰爭他們在怕什么?為何不敢動陜西,

日本全面侵華戰爭之謎:恐懼與野心的交織 在二十世紀三十年代,日本帝國主義以令人發指的暴行和殘忍手段,對中國發動了全面侵華戰爭。然而,在這場戰爭中,有一個引人關注的現象:日本侵略者在進攻過程中&#…