MySQL儲存引擎之Spider核心深度解析
作者介紹
朱閱岸,中國人民大學博士,現供職於騰訊雲資料庫團隊。研究方向主要為資料庫系統理論與實現、新硬體平臺下的資料庫系統以及TP+AP型混合系統。
Spider是為MySQL/MariaDB開發的一個特殊引擎,具有內嵌分片功能。現在它已經被整合到MariaDB10.0及以上版本中,作為MariaDB的一個新的主要特性。Spider的主要功能是將資料分散到多個後端節點,它的作用類似於一個代理。
本文主要分成四個部分來介紹Spider:
1.錶鏈接:利用Spider,多個後端節點的表看起來就像存在於單一例項上一樣。
2.事務:Spider實現了XA事務/單機事務介面,支援XA事務,以便在多個數據節點之間同步或者更新資料。
3.插拔式引擎:Spider作為MySQL/MariaDB的一個插拔式引擎,實現handler類定義的表訪問方法。
4.讀寫流程:受MySQL Server層驅動,執行訪問資料的動作。
一、錶鏈接
Spider的錶鏈接的技術參考ISO/IEC 9075-9:2008 SQL/MED標準。利用Spider的這個特性,你可以像操作本地MariaDB例項的表一樣來操作遠端MariaDB例項上的表,也可以像操作本地MariaDB例項的表一樣來操作分佈在多個MariaDB例項上的表。
當建立一個Spider儲存引擎的表時,該表指向遠端伺服器上對應的一張表或者多個例項上的表,就像UNIX/Linux中的軟連結一樣。遠端伺服器上的表可以是任何儲存引擎的表。在執行CREATE TABLE命令建立Spider引擎的表時,需要新增COMMENT或CONNECTION語法來指定遠端伺服器的地址等資訊。例如,在遠端伺服器(該伺服器是資料節點,假設IP為192.168.0.1)上建立瞭如下一張表:
CREATE TABLE s(id INT NOT NULL AUTO_INCREMENT, code VARHCAR(10), PRIMARY KEY(id));
在Spider節點建立一張表指向該表:
CREATE TABLE s(id INT NOT NULL AUTO_INCREMENT, code VARHCAR(10), PRIMARY KEY(id)) ENGINE=SPIDER COMMENT ‘host “192.168.0.1”,user “user1”, password “pwd1”, port “3307”’
在Spider節點,表字段定義可以忽略。Spider第一次訪問表的時候,如果發現沒有表字段定義,會從後端節點拉取相關元資料,然後快取在本地。
Spider的系統表spider_tables記錄了各個資料分片的位置資訊,類似於程式語言中指標作用。該系統表可以便利Spider跨節點的join操作:訪問資料所在的機器,然後把資料拉取到本地進行join操作;如果進行join操作欄位不是分片欄位,那麼需要廣播SQL語句將資料拉取到Spider節點進行join操作。
Spider_tables類似圖1所示。
圖1. Spider錶鏈接
二、事務
Spider分別針對單機事務與XA事務實現了相應的操作事務的方法。圖2列出了部分實現的方法。
圖2. Spider部分實現的事務介面
上述方法的主要實現是向後端節點發送訊息,有些階段同時需要執行記錄系統表的行為。Spider依賴後端資料節點保證事務的永續性以及隔離性。它只負責開啟事務,以及在適當的時機發送提交或者回滾事務的命令。如果單機事務涉及多個數據節點,Spider需要將相應的連線儲存在佇列中。在事務提交或者回滾的時候,逐個傳送相應的命令。
Spider參照分散式事務DTP/XA模型實現了分散式XA事務(見圖3)。在這個模型中,存在RM(Resource Manager,資源管理器)、TM(Transaction Manager, 事務管理器)以及AP(Application, 應用程式)三種角色。AP通過RM API來操作和管理資源,通過TM介面開啟/終止/結束事務。RM與TM之間需要實現XA介面。XA介面定義了兩階段提交的必要步驟,以及RM與TM之間需要進行的互動。Spider扮演的是TM角色,而後端的資料節點扮演的是RM的角色。
圖3. 分散式DTP/XA模型
為了使用分散式XA事務,業界定義的XA命令如下:
XA START ‘trx-id’; //開啟XA事務
do actual work; //實際的查詢執行語句
XA END ‘trx-id’; //XA事務結束
XA PREPARE ‘trx-id’; //預提交
XA COMMIT ‘trx-id’; //提交
Spider會在系統表spider_xa中記錄XA事務的狀態,同時在另外一張系統表spider_xa_members中記錄參與該XA事務的節點,以便進行操作。
在Spider中,XA事務分別有四種狀態,如圖4所示,對應於NOT YET,PREPAED,ROLLBACK以及COMMITTED。Spider在開始PREPARE階段之際會在系統表spider_xa中標記該XA事務的狀態為NOT YET。在所有資料節點都接收到PREPARE訊息以後,該XA事務的狀態進入到PREPARED階段。假如在PREPARE階段,某一個數據節點發生故障,那麼Spider會回滾該事務。相應地,事務的狀態變成ROLLBACK。
最後,如果所有參與事務的節點都返回PREPARE OK,該事務進入提交階段。圖5給出了對應上述命令的每一個步驟,Spider向後端節點發送的訊息。
圖4. Spider XA事務狀態轉換
圖5. 執行XA事務,Spider與兩個後端節點的互動
從圖5可以看到Spider向後端節點發送XA START命令時會設定會話級別的事務特性,同時將XA事務ID傳送到後端節點。因為XA事務ID由三部分組成,Spider會將這三個部分的解析出來,然後拼接成對應的字串傳送到後端節點。為了節省網路開銷,Spider將XA END與XA PREPARE命令合併起來一起傳送。也就是在這個階段初始,Spider在系統表裡面記錄事務的狀態。如果所有的RM都返回OK,那麼Spider進入PREPARED狀態,準備提交事務。否則,事務進入到回滾狀態。
三、插拔式引擎
MySQL最強大的功能之一,以及區別於其它關係型資料庫系統的一個主要的特色是不同的表能夠採用不同的儲存引擎。每一個儲存引擎都有其優缺點,使用者能夠根據自己的需要定製MySQL的儲存引擎。儲存引擎能夠控制在哪裡以及如何存放、獲取資料。它代表了下面物理層提供的抽象邏輯介面,也是資料庫執行實際I/O操作的地方。這是一個元件體系結構。在這個結構中,handler類定義了儲存引擎提供的介面和功能。因為所有的儲存引擎從基類handler繼承而來,所以它們能夠提供相同的功能。
總的來說,handler類和handlerton結構在整個體系結構中扮演了中間層的角色。你所編寫的儲存引擎只有滿足了handler的要求後,才能順利插入到執行的MySQL伺服器中。所有的網路連線、安全認證、解析和優化由MySQL伺服器本身完成,與儲存引擎無關。
Spider作為MySQL的一個可插拔引擎,實現了handler類定義的相應的存取方法。Spider本身並不存放資料,而是類似一個代理的功能將訪問請求路由到後端的資料節點。Spider提供了兩種途徑訪問後端節點儲存的資料。如圖6所示,Spider可以遵循MySQL傳統的查詢處理流程來訪問資料,也開發了自有的一套來加速資料訪問。在傳統的查詢處理方式下,SQL查詢請求經過查詢解析、查詢重寫、查詢優化等步驟。按照生成的查詢執行計劃,Spider從後端節點拉取資料,交給MySQL伺服器處理。Spider在這種查詢處理框架之下的一個缺點是不能很好地利用後端節點可並行化特性,同時需要對SQL查詢進行兩次解析,帶來的效能損耗問題比較嚴重。
在我們的測試中,效能損耗約50%左右。基於這個原因,為了加速聚集、統計等查詢,Spider開發團隊提供了DirectSQL方式執行查詢。DirectSQL的原理類似於Map Reduce方案,將查詢直接下發到後端節點,無需在MySQL伺服器層進行解析(Map階段);後端節點將結果返回給Spider,由Spider合併結果集。(Reduce階段)。這個方式很好地利用後端節點可並行處理查詢的特點,消除重複解析SQL語句的行為。
圖6. MySQL體系下的Spider
上面已經談到,Spider本身並不儲存資料,因此需要將資料訪問請求轉換成其它方式,例如Handler、Handler Socket以及SQL方式。前面兩種訪問方式更像是一種NoSQL的資料訪問方式,允許查詢繞過SQL layer層。Spider允許後端的資料節點可以是不同的資料庫系統,通過2PC保證事務提交的原子性。
四、讀寫流程
為了更清楚地瞭解Spider的讀寫流程,我們有必要研究一下資料庫系統的查詢執行模型,以及MySQL的插拔式引擎如何跟這個模型對接的。
資料庫系統基本都採用迭代器模型處理查詢,也叫volcano查詢執行引擎(發明這個詞的學者大概是因為查詢執行計劃樹看起來像一座火山,如圖7)。執行計劃樹的上層節點通過get_next方法驅動子節點獲取一條元組,子節點遞迴呼叫。在葉子節點也就是基本表將資料返回。
這個模型的一個好處就是實現起來很優雅,同時資料流與控制流結合在一起方便程式的除錯。這個模型的缺點是函式的大量呼叫使得程序/執行緒上下文切換頻繁,程式的區域性性受到損害。因此,後來針對OLAP場景,採用了向量查詢執行模型來減少程序上下文的切換以及保證保證快取記憶體的命中率。
再次以圖7為例子,圖中的SQL語句的功能是查詢一個部門的平均薪資。假如在職工表EMP的員工ID欄位Dno上存在索引,MySQL在Server層針對該查詢語句生成的查詢計劃如下:順序掃描部門表,通過索引訪問職工表,然後在兩表join操作之後進行投影操作。下一個階段為分組排序操作。上層的操作運算元(例如join),驅動子節點呼叫get_next方法(表掃描方法)獲取一條元組。底層操作運算元(表訪問方法,handler介面定義)將資料返回。至此,我們可以總結一下MySQL體系的工作原理:查詢執行計劃由MySQL Server層生成,儲存引擎受執行計劃驅動而訪問表。MySQL的handler已經定義好表的訪問方法,實現了這些訪問方法的儲存引擎就可以作為MySQL的外掛式引擎而存在。
下面我們對Spider的讀寫流程結合Server層程式碼進行分析。
圖7. 查詢計劃樹示例
1、SELECT操作
上面提到Spider的作用類似一個proxy,本身並不儲存資料。因此Spider處理SELECT語句(UPDATE與DELETE類似)首先需要根據查詢解析的資訊生成一個SELECT語句,傳送到查詢涉及的後端節點,將資料從遠端拉到本地,然後進行處理。函式spider_db_append_select_columns根據查詢涉及的讀集以及寫集獲取相應的欄位,構造一個SQL語句從後端節點拉取資料到本地。如果涉及多個分片,spider將從不同例項獲取過來的結果集存放在不同的結果集spider_db_result中。類spider_db_fetch提供了fetch_next, current_row等方法供上層方法呼叫。Server層呼叫get_next方法驅動引擎層獲取下一條資料。
對於表訪問方法,MySQL實現了索引掃描(ha_index_read)與隨機訪問(ha_rnd_next)的方法。對於切分為多個分片的DB,索引掃描需要藉助優先佇列。索引掃描需要區分是否是第一次呼叫該方法。如果是第一次呼叫該方法,需要遍歷所有的分片讀取一條記錄,然後插入到優先佇列。對應到Spider,如果第一次呼叫訪問遠端例項表的方法,需要生成SELECT語句,將遠端例項的資料拉到本地存放。在使用索引掃描的情況,MySQL 為每個分片保留一個key buffer以及record buffer。server 利用佇列頭部的m_top_entry 獲得訪問的分片ID。接著,呼叫get_next方法獲取相應的元組,將返回的資料存放在record buffer,並插入到優先佇列。函式最後將元組從優先佇列返回。
為緩解記憶體等資源的壓力,Spider實現全表掃描的方法是逐個分片序列掃描(為了加速,spider也提供了並行掃描資料節點的選項)。圖8給出了Spider對於上述兩種表訪問方法的實現機制。
圖8-1. 索引掃描實現
圖8-2. 全表掃描
2、INSERT操作
MySQL的handler類對於INSERT操作提供的介面函式的名字是write_row。儲存引擎想要支援INSERT操作就必須實現write_row方法。Spider對於write_row方法的實現是簡單地根據查詢解析的資訊拼接一條INSERT語句,發往後端節點處理。如果是批量插入操作則需要與MySQL Server層配合,將INSERT語句批量發到後端節點。
圖9結合一條批量插入的INSERT語句給出MySQL中INSERT操作的具體實現。
mysql_insert呼叫write_row執行具體的插入操作(第8行)。這是儲存引擎必須實現的方法。對應於spider,spider根據查詢涉及到的列(field)拼成一條INSERT語句(如果是分片資料庫,VALUSE中的列必須包含分割槽鍵,分割槽鍵是自增列的情況除外)。圖9中的QUERY將使用者ID(ID)和使用者名稱(Name)插入到user表,其中ID是分割槽鍵。mysql_insert根據VALUES包含的元組數目,判斷是否需要進行批量插入操作。該例子的QUERY的VALUES包含4條元組,所有需要進行批量插入操作。MySQL迴圈呼叫write_row方法觸發spider生成INSERT語句。Spider的write_row方法實現中會根據分割槽鍵將INSERT語句進行分組(第5行~第9行)。圖9給出的例項只有兩個資料分片,所以SQL語句被分成兩組。處理完VALUES以後,Spider的INSERT語句也拼接完成。
ha_end_bulk_insert方法通知Spider完成VALUES處理。此時,Spider將INSERT傳送到後端節點進行處理(第11行)。
圖9. Spider中INSERT操作的實現
3、DELETE實現
Spider想要支援DELETE操作必須實現MySQL handler類提供的ha_delete_row方法。與INSERT操作不同,DELETE操作需要生成一條SELECT語句將查詢涉及的分割槽鍵拉到Spider節點。這是因為MySQL Server層的“once-a-tuple”的查詢執行模型(實際上基本所有的關係資料庫系統都採用該模型)會驅動Spider逐個拼接DELETE語句,然後發往後端節點。這時候,Spider需要知道對應的DELETE語句該往哪個後端節點發送。為了減少網路開銷,Spider提供了批量傳送DELETE語句的功能。
圖10. DELETE實現
圖10給出了Spiderpider中delete的實現。MySQL Server層首先確定表的訪問方法:採用索引掃描或者全部掃描(第5行)?DELETE方法需要執行一次查詢操作,呼叫get_next方法(info.read_record)獲取一條元組(第10行)。Spider需要判斷是否第一次呼叫get_next方法。如果是的話,則需要生成SELECT語句,將資料節點的資料拉到本地。否則,Spider直接從本地返回資料給上層呼叫者。接下來,Server層呼叫ha_delete_row方法將資料刪除。這是儲存引擎需要具體實現的方法。由於Spider本身並不儲存資料的緣故,其實現delete操作主要思想是利用從後端節點拉取過來的資料(分割槽鍵,過濾條件等),拼接成一條DELETE語句。然後,傳送該請求到資料節點。Spider為了優化網路開銷,提供了批量傳送DELETE語句的選項。
UPDATE操作的實現類似DELETE,都需要Spider生成SELECT語句從後端節點拉取資料。只不過,UPDATE在更新區分鍵的時候,可能需要多一次DELETE操作(刪除原來分割槽的資料,將新的資料插入到不同的分割槽)。
總結
Spider的最大亮點是為MySQL的使用者提供分庫分表的中介軟體解決方案,同時在SQL語法上相容MySQL。這得益於Spider作為MySQL的插拔式引擎而存在。Spider是一個proxy,其本身並沒有儲存資料,因此上層的讀寫表請求需要轉換成SQL語句,重新路由到後端的資料節點。相比其它的中介軟體解決方案,Spider的查詢解析次數都是兩次,並沒有過多開銷。此外,Spider還針對聚集、排序等操作提供了MAP REDUCE的解決方案。
總之,從相容性、效能上衡量,Spider是MySQL分庫分表一個不錯的選項。
文章來自微信公眾號:DBAplus社群