關於MySQL8.0 InnoDB並行執行的詳解

藏色散人
發布: 2020-03-27 08:59:10
轉載
2718 人瀏覽過

概述

MySQL經過多年的發展已然成為最受歡迎的資料庫,廣泛用於網路產業,並逐步向各個傳統產業滲透。之所以流行,一方面是其優秀的高並發事務處理的能力,另一方面也得益於MySQL豐富的生態。 MySQL在處理OLTP場景下的短查詢效果很好,但對於複雜大查詢則能力有限。最直接一點就是,對於一個SQL語句,MySQL最多只能用一個CPU核心來處理,在這種場景下無法發揮主機CPU多核心的能力。 MySQL沒有停滯不前,一直在發展,新推出的8.0.14版本第一次引入了平行查詢特性,使得check table和select count(*)類型的語句效能成倍提升。雖然目前使用場景還比較有限,但後續的發展值得期待。

推薦:《mysql影片教學

使用方式

透過設定參數innodb_parallel_read_threads來設定並發執行緒數,就能開始並行掃描功能,預設這個值為4。我在這裡做一個簡單的實驗,透過sysbench導入2億個數據,分別配置innodb_parallel_read_threads為1,2,4,8,16,32,64,測試並行執行的效果。測試語句為select count(*) from sbtest1;

關於MySQL8.0 InnoDB並行執行的詳解

#橫軸是配置並發執行緒數,縱軸是語句執行時間。從測試結果來看,整個並行表現還是不錯的,掃描2億筆記錄,從單線程的18s,下降到32線程的1s。後面並發開再多,由於資料量有限,多執行緒的管理消耗超過了並發帶來的效能提升,不能再繼續縮短SQL執行時間。

MySQL並行執行

其實目前MySQL的平行執行仍處於非常初級階段,如下圖所示,左邊是之前MySQL串列處理單一SQL形態;中間的是目前MySQL版本提供的平行能力,InnoDB引擎並行掃描的形態;最右邊的是未來MySQL要發展的形態,優化器根據系統負載和SQL產生並行計劃,並將分區計劃下發給執行器並行執行。並行執行不僅僅是並行掃描,還包括並行聚集,並行連接,並行分組,以及並行排序等。目前版本MySQL的上層的優化器以及執行器並沒有配套的修改。因此,下文的討論主要集中在InnoDB引擎如何實現並行掃描,主要包括分區,並行掃描,預讀以及與執行器互動的適配器類別。

關於MySQL8.0 InnoDB並行執行的詳解

分區

並行掃描的一個核心步驟就是分區,將掃描的資料分割成多份,讓多個執行緒並行掃描。 InnoDB引擎是索引組織表,資料以B tree的形式儲存在磁碟上,節點的單位是頁面(block/page),同時緩衝池中會對熱點頁面進行緩存,並透過LRU演算法進行淘汰。分區的邏輯就是,從根節點頁面出發,逐層往下掃描,當判斷某一層的分支數超過了配置的執行緒數,則停止拆分。在實現時,實際上總共會進行兩次分區,第一次是按根節點頁的分支數劃分分區,每個分支的最左葉子節點的記錄為左下界,並將這個記錄記為相鄰上一個分支的右上界。透過這種方式,將B tree劃分成若干子樹,每個子樹就是一個掃描分割區。經過第一次分區後,可能出現分區數不能充分利用多核心問題,例如配置了並行掃描線程為3,第一次分區後,產生了4個分區,那麼前3個分區並行做完後,第4個分區至多只有一個執行緒掃描,最終效果就是無法充分利用多核心資源。

二次分區

為了解決這個問題,8.0.17版本引入了二次分區,對於第4個分區,繼續下探拆分,這樣多個子分區又能並發掃描,InnoDB引擎並發掃描的最小粒度是頁面層級。具體判斷二次分區的邏輯是,一次分區後,若分區數大於線程數,則編號大於線程數的分區,需要繼續進行二次分區;若分區數小於線程數且B tree層次很深,則所有的分區都需要進行二次分區。

相關程式碼如下:

split_point = 0;
if (ranges.size() > max_threads()) {
   //最后一批分区进行二次分区                                      
   split_point = (ranges.size() / max_threads()) * max_threads();          
 } else if (m_depth < SPLIT_THRESHOLD) {                                  
   /* If the tree is not very deep then don&#39;t split. For smaller tables    
   it is more expensive to split because we end up traversing more blocks*/
   split_point = max_threads();                                            
 } else {
   //如果B+tree的层次很深(层数大于或等于3,数据量很大),则所有分区都需要进行二次分区
 }
登入後複製

無論是一次分區,還是二次分區,分區邊界的邏輯都一樣,以每個分區的最左葉子節點的記錄為左下界,並且將這個記錄記為相鄰上一個分支的右上界。這樣確保分區夠多,粒度夠細,充分並行。下圖展示了配置為3的並發線程,掃描進行二次分區的情況。

相關程式碼如下:

關於MySQL8.0 InnoDB並行執行的詳解

create_ranges(size_t depth, size_t level)
一次分区:
parallel_check_table
 add_scan
   partition(scan_range, level=0)  /* start at root-page */
     create_ranges(scan_range, depth=0, level=0)
   create_contexts(range, index >= split_point)
二次分区:                                                      
split()
 partition(scan_range, level=1)
   create_ranges(depth=0,level)
登入後複製

並行掃描

#

在一次分區後,將每個分區掃描任務放入到一個lock-free隊列中,並行的worker線程從隊列中獲取任務,執行掃描任務,如果獲取的任務帶有split屬性,這個時候worker會將任務進行二次拆分,並投入佇列。這個過程主要包括兩個核心接口,一個是工作線程接口,另外一個是遍歷記錄接口,前者從隊列中獲取任務並執行,並維護統計計數;後者根據可見性獲取合適的記錄,並通過上層注入的回呼函數處理,例如計數等。

Parallel_reader::worker(size_t thread_id)

{

 1.從ctx-queue提取ctx任務

# 2.根據ctx的split屬性,決定是否需要進一步拆分分區(split())

 3.遍歷分區所有記錄(traverse())

 4.一個分區任務結束後,維護m_n_completed計數

 5.如果m_n_compeleted計數達到ctx數目,喚醒所有worker執行緒結束

 6.根據traverse接口,回傳err訊息。

}

Parallel_reader::Ctx::traverse()

#{

## 1.根據range設定pcursor

# 2.找到btree,將遊標定位到range的起始位置

 3.判斷可見度(check_visibility)

 4.如果可見,根據回呼函數計算(例如統計)

5.向後遍歷,若達到了頁面的最後一筆記錄,啟動預讀機制(submit_read_ahead)

 6.超出範圍後結束

}

#同時在8.0 .17版本也引進了預讀機制,避免因為IO瓶頸導致並行效果不佳的問題。目前預讀的線程數不能配置,在程式碼中硬編碼為2個線程。每次預讀的單位是一個簇(InnoDB檔案通過段,簇,頁三級結構管理,一個簇是一組連續的頁),根據頁面配置的大小,可能為1M或2M。對於常見的16k頁面配置,每次預讀1M,也就是64個頁面。 worker執行緒在進行掃描時,會先判斷相鄰的下一個頁面是否為簇的第一個頁面,如果是,則會啟動預讀任務。預讀任務同樣透過lock-free 佇列緩存,worker執行緒是生產者,read-ahead-worker是消費者。由於所有分割區頁面沒有重疊,因此預讀任務也不會重複。

執行器互動(適配器)

實際上,MySQL已經封裝了一個適配器類別Parallel_reader_adapter來供上層使用,為後續的更豐富的平行執行做準備。首先這個類別要解決記錄格式的問題,將引擎層掃描的記錄轉換成MySQL格式,這樣做到上下層解耦,執行器不用感知引擎層格式,統一以MySQL格式處理。整個過程是一個管線,透過一個buffer批量儲存MySQL記錄,worker執行緒不停的將記錄從引擎層上讀上來,同時有記錄不停的被上層處理,透過buffer可以平衡讀取和處理速度的差異,確保整個過程流動起來。快取大小預設是2M,根據表的記錄行長來決定buffer可以快取多少個MySQL記錄。核心流程主要在process_rows介面中,流程如下

process_rows

{

 1.將引擎記錄轉換成MySQL記錄

# 2.取得本執行緒的buffer訊息(轉換了多少mysql記錄,發送了多少給上層)

 3.將MySQL記錄填入buffer,自增統計m_n_read

 4.呼叫回呼函數處理(例如統計,聚合,排序等),自增統計m_n_send

}

對於呼叫者來說,需要設定表的元信息,以及注入處理記錄回調函數,例如處理聚集,排序,分組的工作。回調函數透過設定m_init_fn,m_load_fn和m_end_fn來控制。

總結

MySQL8.0引入了平行查詢雖然也比較初級,但已經讓我們看到了MySQL並行查詢的潛力,從實驗中我們也看到了開啟並行執行後,SQL語句執行充分發揮了多核心能力,回應時間急遽下降。相信在不久的將來,8.0的會支援更多並行算子,包括並行聚集,並行連接,並行分組以及並行排序等。

以上是關於MySQL8.0 InnoDB並行執行的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:cnblogs.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!