溫故知新--EverDB之分布式執(zhí)行計劃
?在數(shù)據(jù)庫系統(tǒng)設(shè)計中,執(zhí)行計劃是對SQL執(zhí)行流程的形式化描述,包括了SQL執(zhí)行需要的所有算子以及其執(zhí)行次序。我們通過“EXPLAIN + SQL”指令可以詳細(xì)地查看其執(zhí)行計劃,找到性能瓶頸,為我們優(yōu)化SQL提供方向和依據(jù)。本文將從EverDB分布式數(shù)據(jù)庫角度闡述執(zhí)行計劃。
(一)分布式架構(gòu)執(zhí)行計劃
相比于集中式數(shù)據(jù)庫,分布式數(shù)據(jù)庫擁有大量分片節(jié)點,分別負(fù)責(zé)各自分片的數(shù)據(jù)計算與存儲,那么其執(zhí)行計劃就需要特殊的實現(xiàn)方式。對于中間件架構(gòu)分布式數(shù)據(jù)庫,通過引入分布式算子(即下文EverDB執(zhí)行計劃節(jié)點)實現(xiàn)數(shù)據(jù)分片存儲功能,執(zhí)行計劃解析優(yōu)化,下發(fā)數(shù)據(jù)分片內(nèi)部獨立計算,協(xié)調(diào)數(shù)據(jù)分片之間并發(fā)執(zhí)行,執(zhí)行結(jié)果由中間件進行進一步整合進行分組、排序等操作,是一種高效便捷的實現(xiàn)方式。
EverDB正是基于這種設(shè)計思路實現(xiàn)的執(zhí)行計劃。與傳統(tǒng)集中式數(shù)據(jù)庫相比,EverDB執(zhí)行計劃使數(shù)據(jù)庫有更高的擴展性,支持更大量級的數(shù)據(jù)規(guī)模,更高并發(fā)的數(shù)據(jù)訪問。在處理相同負(fù)載壓力的前提下,可以充分利用各分片的存儲與計算資源,以及并行計算的優(yōu)勢達到更好的性能。
(二)EverDB執(zhí)行計劃
EverDB分布式數(shù)據(jù)庫由Grid調(diào)度層、數(shù)據(jù)節(jié)點、配置節(jié)點、管理臺組成。Grid調(diào)度層作為分布式數(shù)據(jù)庫的調(diào)度節(jié)點,接收并解析SQL,將SQL語句重構(gòu)改造,支持涉及分片表和非分片表兩種類型的執(zhí)行計劃分析。
圖1
EverDB的執(zhí)行計劃包括SQL在Grid調(diào)度層和后端數(shù)據(jù)節(jié)點的執(zhí)行流程。Grid調(diào)度節(jié)點的執(zhí)行計劃,主要涉及邏輯處理層和連接驅(qū)動層兩個部分,其中邏輯處理層包括詞法、語法解析模塊,客戶端通信模塊,普通表/分片表配置、SQL重構(gòu)改造、執(zhí)行計劃樹及計劃樹節(jié)點。其中普通表/分片表配置用于識別SQL是否需要分片處理,并獲取分片表的存儲地址信息,完成基于分片策略的執(zhí)行計劃構(gòu)建。連接驅(qū)動層是內(nèi)部連接池和通信協(xié)議的處理模塊,完美支持MySQL通信協(xié)議,負(fù)責(zé)在執(zhí)行計劃中將請求下推給數(shù)據(jù)節(jié)點。數(shù)據(jù)節(jié)點執(zhí)行計劃的實現(xiàn)方式可以參照MySQL執(zhí)行計劃。
圖2
以分片查詢?yōu)槔?,EverDB的Grid調(diào)度節(jié)點的執(zhí)行計劃流程:
SQL解析:客戶端處理線程接收到從客戶端發(fā)來的查詢請求,對SQL進行詞法語法解析。
SQL重構(gòu):根據(jù)SELECT查詢表的存儲信息,可分為普通表和分片表,如果是分片表,需要進一步根據(jù)查詢條件和數(shù)據(jù)存儲情況,重構(gòu)優(yōu)化SQL語句。比如,多分片間的跨節(jié)點查詢,可通過SQL重構(gòu)后下推數(shù)據(jù)節(jié)點執(zhí)行,或者通過建立臨時表,遷移小部分?jǐn)?shù)據(jù)來降低查詢性能損耗。
構(gòu)建執(zhí)行計劃:SQL經(jīng)過解析,需要構(gòu)建對應(yīng)的執(zhí)行計劃樹,即用于維護SQL執(zhí)行計劃的數(shù)據(jù)結(jié)構(gòu),由多個執(zhí)行計劃節(jié)點構(gòu)成。執(zhí)行計劃節(jié)點是SQL執(zhí)行過程中每一步操作的執(zhí)行者,也可以看作一個個線程的執(zhí)行體,它分為很多類型,用于執(zhí)行不同的操作,比如內(nèi)部執(zhí)行節(jié)點、事務(wù)執(zhí)行計劃節(jié)點、數(shù)據(jù)遷移執(zhí)行節(jié)點、信息查詢節(jié)點、信息發(fā)送節(jié)點、組合排序去重節(jié)點等。
運行執(zhí)行計劃:執(zhí)行計劃運行過程中,對于分片表查詢,采用多線程并發(fā)的方式,加快分布式集群的處理速度。
SQL下推:為將查詢請求下推至對應(yīng)的分片數(shù)據(jù)節(jié)點,EverDB通過通信模塊(即圖3中的MySQL協(xié)議適配、驅(qū)動模塊)將查詢請求以MySQL通信協(xié)議的格式封裝成數(shù)據(jù)包,再由連接池分配的連接將數(shù)據(jù)包發(fā)送給數(shù)據(jù)節(jié)點,以完成分片查詢請求的下推。
整合結(jié)果:數(shù)據(jù)節(jié)點接收到來自調(diào)度節(jié)點的請求,進行進一步的SQL解析,形成針對表的執(zhí)行計劃。查詢計算完成后,數(shù)據(jù)節(jié)點將查詢結(jié)果反饋至調(diào)度節(jié)點,由調(diào)度節(jié)點繼續(xù)按執(zhí)行計劃樹,對所有數(shù)據(jù)節(jié)點返回的分片結(jié)果進行歸并、排序等操作,將完整的查詢結(jié)果返回給客戶端,完成查詢請求。
圖3
調(diào)度節(jié)點在生成執(zhí)行計劃樹時,會根據(jù)分片規(guī)則對語句進行并行執(zhí)行改造,將重構(gòu)后的多條SQL由對應(yīng)的執(zhí)行計劃樹葉節(jié)點下推至目標(biāo)實例,由數(shù)據(jù)節(jié)點實例完成該分片的查詢執(zhí)行計劃分析。
圖4介紹了執(zhí)行計劃葉節(jié)點將查詢請求下推至數(shù)據(jù)節(jié)點的通訊流程。COM_QUERY是封裝了查詢語句的協(xié)議包,由執(zhí)行計劃樹葉節(jié)點發(fā)送至對應(yīng)的數(shù)據(jù)節(jié)點進行查詢計算。執(zhí)行計劃葉節(jié)點以MySQL協(xié)議流程接收、解析結(jié)果集。圖示中結(jié)果集返回的協(xié)議包及次序為:
ResultSetHead:結(jié)果集頭包,包含列個數(shù)信息;
Field:結(jié)果集字段包,包含每一字段具體的信息,結(jié)果集每一字段對應(yīng)一個Field協(xié)議包;
所有字段信息發(fā)送結(jié)束后,后端數(shù)據(jù)節(jié)點發(fā)送一個 EOF 協(xié)議包,開始行數(shù)據(jù)的發(fā)送;
RowData:結(jié)果集行數(shù)據(jù)包,與Field協(xié)議包相同,每一行的數(shù)據(jù)對應(yīng)一個行數(shù)據(jù)包,因此,一次結(jié)果集發(fā)送可能會包含多個行數(shù)據(jù)協(xié)議包;
所有行數(shù)據(jù)包發(fā)送完畢后,服務(wù)端會再發(fā)送 EOF 協(xié)議包表示結(jié)果集發(fā)送的結(jié)束;
執(zhí)行計劃葉節(jié)點收到分片的查詢結(jié)果后,將各自分片結(jié)果交由父級非葉節(jié)點對所有分片結(jié)果做進一步處理(如歸并、排序等),向客戶端返回完整的查詢數(shù)據(jù)結(jié)果。
圖4
(三) 如何查看執(zhí)行計劃?
展示執(zhí)行計劃,只需在查詢的SELECT關(guān)鍵字之前增加DBSCALE EXPLAIN。具體語法如下:
DBSCALE EXPLAIN + SELECT查詢語句;
結(jié)果包含執(zhí)行計劃每一步的執(zhí)行信息,顯示執(zhí)行節(jié)點、執(zhí)行次序和執(zhí)行SQL內(nèi)容,SQL性能好壞也能通過執(zhí)行計劃看出來。用于分析SQL語句和表結(jié)構(gòu)的性能瓶頸。
圖5
如上圖(圖5)示例,執(zhí)行計劃返回結(jié)果分為上下兩個結(jié)果集。第一部分展示的是查詢請求從中間層Grid到數(shù)據(jù)節(jié)點的完整執(zhí)行計劃。結(jié)果集前兩列是SQL在中間層Grid的執(zhí)行計劃,即exec_node字段展示SQL的執(zhí)行計劃樹,data_source展示的是每一個分片執(zhí)行節(jié)點涉及的分片數(shù)據(jù)源。結(jié)果集其他字段則展示的是每一條分片查詢在各自數(shù)據(jù)節(jié)點上的執(zhí)行計劃,這塊與MySQLexplain的返回結(jié)果是相同的。第二部分展示的是執(zhí)行計劃在每個執(zhí)行節(jié)點上實際運行的重構(gòu)后SQL語句,因此可能與從客戶端接收到的SQL語句不同。
執(zhí)行計劃中一些重要字段的說明如下:
- exec_node:執(zhí)行計劃樹的每一個執(zhí)行節(jié)點。整列展示了完整的執(zhí)行計劃樹,以“*”開頭表示執(zhí)行計劃樹根節(jié)點,“-”開頭表示執(zhí)行計劃樹子節(jié)點,其中短橫線越長表示節(jié)點層數(shù)越深。如上文示例包含*MySQLSendNodeid首字母為*號,是此例分片查詢執(zhí)行計劃樹的根節(jié)點。--MySQLFetchNode以“--”開頭,是執(zhí)行計劃樹的子節(jié)點,多個FetchNode并發(fā)查詢對應(yīng)數(shù)據(jù)節(jié)點的數(shù)據(jù)分片,再由SendNode整合多個FetchNode的查詢結(jié)果。
- data_source:數(shù)據(jù)源信息。數(shù)據(jù)源是提供數(shù)據(jù)庫連接用來具體執(zhí)行客戶端請求的數(shù)據(jù)庫實例,即MySQLFetchNode執(zhí)行查詢的實例地址。
- id:查詢中執(zhí)行select子句或操作表的順序,id相同,執(zhí)行順序由上至下;id不同,id值越大優(yōu)先級越高,越先被執(zhí)行。
- select_type:查詢數(shù)據(jù)的操作類型,如下表:
SIMPLE | 查詢中不包含子查詢或者UNION |
PRIMARY | 查詢中若包含任何復(fù)雜的子部分,最外層查詢標(biāo)記為PRIMARY |
SUBQUERY | 在SELECT或WHERE列表中包含了子查詢,該子查詢被標(biāo)記為SUBQUERY |
DERIVED | 在FROM列表中包含的子查詢被標(biāo)記為DERIVED(衍生) |
UNION | 若第二個SELECT出現(xiàn)在UNION之后,則被標(biāo)記為UNION;若UNION包含在FROM子句的子查詢中,外層SELECT將被標(biāo)記為DERIVED |
UNION RESULT | 從UNION表獲取結(jié)果的SELECT被標(biāo)記為UNION RESULT |
- table:執(zhí)行節(jié)點所處理的表名。
- type:數(shù)據(jù)節(jié)點在表中找到所需行的方式,又稱“訪問類型”,表示| All | index | range | ref | eq_ref | const,system | null | 由左至右,由最差到最好。常見類型如下表:
ALL | Full Table Scan, 數(shù)據(jù)節(jié)點將遍歷全表以找到匹配的行 |
Index | Full Index Scan,index與ALL區(qū)別為index類型只遍歷索引樹 |
Range | 索引范圍掃描,對索引的掃描開始于某一點,返回匹配值域的行,常見于between、<、>等的查詢 |
Ref | 非唯一性索引掃描,返回匹配某個單獨值的所有行。常見于使用非唯一索引即唯一索引的非唯一前綴進行的查找 |
Eq_ref | 唯一性索引掃描,對于每個索引鍵,表中只有一條記錄與之匹配;常見于主鍵或唯一索引掃描 |
Const、system | 當(dāng)數(shù)據(jù)節(jié)點對查詢某部分進行優(yōu)化,并轉(zhuǎn)換為一個常量時,使用這些類型訪問;如將主鍵置于where列表中,數(shù)據(jù)節(jié)點就能將該查詢轉(zhuǎn)換為一個常量,system是const類型的特例,當(dāng)查詢的表只有一行的情況下,使用system |
NULL | 數(shù)據(jù)節(jié)點在優(yōu)化過程中分解語句,執(zhí)行時甚至不用訪問表或索引 |
- possible_keys:指出數(shù)據(jù)節(jié)點能使用哪個索引在表中找到行,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用。
- key:顯示數(shù)據(jù)節(jié)點在查詢中實際使用的索引,若沒有使用索引,顯示為NULLNote:查詢中若使用了覆蓋索引,則該索引僅出現(xiàn)在key列表中。
- key_len:表示索引中使用的字節(jié)數(shù),可通過該列計算查詢中使用的索引的長度。key_len顯示的值為索引字段的最大可能長度,并非實際使用長度,即key_len是根據(jù)表定義計算而得,不是通過表內(nèi)檢索出的。
- ref:表示上述表的連接匹配條件,即哪些列或常量被用于查找索引列上的值。
- rows:表示數(shù)據(jù)節(jié)點根據(jù)表統(tǒng)計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數(shù)。
- Extra:數(shù)據(jù)節(jié)點解決查詢的詳細(xì)信息,盡量避免出現(xiàn):Using File Sort、Using Temporary。
第二部分包括node_id和sql兩個字段:node_id與第一部分中exec_node字段的中括號內(nèi)序號相關(guān)聯(lián),表示exec_node每個層次中具體執(zhí)行SQL語句。具體SQL語句內(nèi)容則在“sql”字段中顯示。
當(dāng)“sql”字段中出現(xiàn)臨時表dbscale_tmp時(dbscale_tmp為EverDB保留字),說明當(dāng)前SELECT查詢涉及跨分片查詢,系統(tǒng)性能損耗較高,需要進一步分析SQL語句和表結(jié)構(gòu)性能瓶頸,盡可能避免使用臨時表,示例如下。
? ?
圖6
四 總結(jié)
EverDB作為一種典型的基于中間件實現(xiàn)分庫分表方案的分布式數(shù)據(jù)庫產(chǎn)品,其執(zhí)行計劃相比于傳統(tǒng)集中式數(shù)據(jù)庫的不同之處在于,既包括了SQL在底層各分片表上的執(zhí)行步驟,也包含proxy如何將SQL進行分布式處理,提高分布式數(shù)據(jù)庫的處理性能,是EverDB基于中間件對執(zhí)行計劃一種特有的實現(xiàn)方式。
EverDB執(zhí)行計劃不管從底層數(shù)據(jù)節(jié)點還是中間層,SQL優(yōu)化算法方面,還有很多值得優(yōu)化改進的地方。未來,EverDB會持續(xù)精進自身的各項能力,努力成為更出色的國產(chǎn)分布式數(shù)據(jù)庫產(chǎn)品。?