開發與維運

OceanBase源碼解讀(二):SQL的一生

竹翁,OceanBase 內核研發總監
楊志豐,花名竹翁,畢業於北京大學,長期從事分佈式系統和數據庫的研發工作,現於阿里巴巴/螞蟻金服自主研發的分佈式關係數據庫 OceanBase 團隊負責研發工作,致力於把設計先進的 HTAP 數據庫系統打造成技術業內標杆的核心基礎設施。在 OceanBase 系統中,他先後負責研究 OceanBase 的 SQL 引擎、分佈式主控模塊、多模數據庫方向以及 OceanBase 的數據庫平臺產品研發,並於近期開始負責內核創新研發工作。竹翁對 C++、分佈式系統原理、SQL 查詢處理、事務處理、編譯技術、工程效率等方面具有深入的理解。

引言

源碼是OceanBase的“方向盤”,本系列主要圍繞“源碼解讀”,通過文章闡述,幫助大家理清數據庫的內在本質。帶你讀源碼第一篇《戳這裡回顧:OceanBase數據庫源碼解讀之模塊結構》為大家介紹了OceanBase數據庫的整體架構,旨在幫助大家快速釐清OceanBase的模塊構成以及各模塊的功能。

本文為 OceanBase 數據庫源碼解讀系列文章的第二篇,將主要為大家介紹 OceanBase 數據庫中一條 SQL 的執行流程主路徑,包括接收、處理、返回結果給客戶端的過程,與開發者們一起探討OceanBase的SQL引擎模塊。

正文

1.png

可以看到,在src/observer 目錄下,內含三個子目錄。其中,omt 中的 mt 表示multi-tenant,內部實現了 observer 線程模型的抽象 worker,每個租戶在其有租戶的節點上會創建一個線程池用於處理 SQL 請求。virtual_table 目錄下是 sys 租戶各個 __all_virtual 虛擬表的實現,“虛擬表”其實是 一種view,它把一些內存數據結構抽象成表接口暴露出來,用於診斷調試等。MySQL 目錄則是 MySQL 協議層,實現了 MySQL 5. 6兼容的消息處理協議。

2.png

除了建立和斷開連接,MySQL 協議大多是簡單的請求響應模型。每種請求類似一個 COM_XXX 命令,每種命令的處理函數對應本目錄一個相應的類,見下圖。

3.png

比如最常用的COM _QUERY 表示一條SQL 請求,處理類位於 obmp_query.h/cpp 中。一般典型的交互過程是 connect、query、query... query、quit。

值得注意的是,所有的 SQL 語句類型,包括 DML、DDL 以及multi-statement 都是用 query 進行命令處理。

建立連接的過程在 obmp_connect,它執行用戶認證鑑權,如果鑑權成功,系統則會創建一個 ObSQLSession 對象(位於 src/sql/session),用於表示唯一一個數據庫的連接,所有其他命令處理都會訪問該 session 對象。

4.png

上圖是 query 的處理類ObMPQuery,它是一條 SQL一生的開始,process 方法為其指引入口。

Mpquery會在其入口的地方將類TraceId初始化。TraceId是一個工具類,放置於線程局部變量中,SQL語句在後續處理過程中所涉及的所有模塊都可以全局訪問。它是一條 SQL 一次處理過程的一個唯一標識,如果執行過程中切換了線程,或者執行了 RPC,都會帶上TraceId。在 oblog 打印的所有調試日誌中,都包含一個以 Y 開頭的十六進制串(猜猜為什麼以 Y 開頭),這就是 TraceId,它可以把不同位置打印的日誌串起來。OceanBase 研發同學查問題時都習慣 grep 到 TraceId 相關的所有日誌。

 

如果一條 SQL 串的格式是stmt;stmt;,即 multi-statement,其表示的是 MySQL 協議的一種特殊優化,可以一次發送多條語句執行,並返回多個結果集(如果有的情況下)。根據SQL是否為多語句,observer會有不同的處理。如果是一個 autocommit=1 的 DML 單語句,由於系統要執行事務提交寫日誌,待執行完成才能響應客戶端。為了儘快讓出線程資源,observer會掛起相關上下文,在日誌提交成功之後執行回調。這裡一些特殊的代碼邏輯就是在處理這個優化。

 

對於多語句的處理,首先ObParser通過一個快速解析入口 split_multiple_stmt 把每條語句拆分出來,再對每條語句進行process_single_stmt。事務控制邏輯我們可以暫時不做考慮,最後 通過do_process,observer進入了 SQL 模塊。

5.png

一般而言,SQL 的模塊劃分非常清晰。總入口是 sql/ob_sql.h/cpp 的 ObSql 類。do_process 會調用這個類的 stmt_query 方法:輸入 SQL 語句字符串,輸出一個包含物理執行計劃和元信息的 ResultSet。外層打開並迭代結果集,把每一行結果發送給客戶端。所以,協議和執行計劃處理本身是“流式”的,並不需要查詢到全部結果才返回客戶端。

 

SQL子模塊

接下來我們再來看看 SQL 的子模塊。parser 模塊執行語法分析,把 SQL 字符串解析為一個 ParseNode 組成的抽象語法樹。其接口類是 ObParser 類。parser 是由 bison 和flex 生成的 C 語言代碼,OceanBase 的 C 語言代碼就位於這裡。parser 有一種快速解析模式,目標不是產生語法樹,而是把 SQL 字符串參數化,具體原理我們將在後續的系列中進行詳細的解釋。

6.png

parser解析之後就交給了resolver。因為抽象語法樹沒有 SQL 語義,所以resolver 對它進行分析,結合數據字典元信息(OceanBase 的代碼叫 schema 模塊),賦予其 SQL 語義。大部分語義報錯就是在這個階段產生的,比如“表已經存在”,“主鍵長度超過限制”等。resolver 模塊的接口類是ObResolver,它的輸出是 ObStmt。這個模塊是面向對象設計的,每種語句類型有一個 Resolver 和一個 Stmt。按照語句類型,分別位於不同的子目錄,如dml、ddl、tcl 等。

 

對於非 SELECT 和 DML 之外的語句,如大多數 DDL 語句解析到這裡便可以執行了。這類簡單語句類型統稱為“命令”,由 engine/cmd 目錄下的 executor 直接執行。DDL 是通過 rootservice(RS)進行執行,所以其 executor 實際是發送 RPC,事務控制語句則在本機直接調用事務層。

 

對於 SELECT 和 DML 及帶數據操作的 DDL,則需要產生執行計劃。優化器(sql/optimizer)的接口類是 ObOptimizer,以上一步生成的 ObDMLStmt(含 SELECT)為輸入,執行基於代價的優化,生成一個邏輯執行計劃(ObLogPlan)。邏輯執行計劃是由 OceanBase 的關係運算算子 ObLogicalOperator 組成的樹狀結構,改寫(sql/rewrite)是優化器的一部分,執行等價的關係運算改寫,產生潛在更好的執行計劃候選。這裡會涉及到一系列改寫規則,改寫規則的入口類是 ObTransformerImpl,輸入輸出都是 ObDMLStmt。

 

接下來,code_generator(cg) 模塊負責把邏輯執行計劃轉換為能夠高效執行的物理執行計劃。它的接口類是 ObCodeGenerator,其中,比較複雜的是表達式的生成過程。engine 目錄下是 SQL 執行引擎,也叫做物理執行計劃(ObPhysicalPlan),它是由物理算子(ObPhyOperator)組成的樹狀結構,執行過程是一種火山模型的流水線。

 

同一個物理執行計劃可以被多個線程並行執行,上一步cg 產生的物理執行計劃一般會被保存到計劃緩存(即 sql/plan_cache 目錄)中。前文提及的parser 有一種特殊的快速解析模式,快速解析後的 SQL 會被嘗試從計劃緩存中直接“撈”可用的物理執行計劃。如果沒有合適的計劃,observer才會執行上文所述的從 resolver 到 cg 的“硬解析”流程。

 

SQL 的一生很長,在這篇文章中,我們僅僅只是帶大家粗略的走了個 “閉環”,在後續的源碼解讀第三篇我們將會帶大家瞭解OceanBase的存儲層,為大家講透分區的一生,敬請期待。

 

如果您有任何疑問,可以通過以下方式與我們進行交流:

微信群:掃碼添加小助手,將拉你進群喲~

1FC2E8C4-ABB4-4b92-9034-052F76E6345C.png

github:https://github.com/oceanbase/oceanbase

博客問答:https://open.oceanbase.com/answer

gitee:https://gitee.com/oceanbase

Leave a Reply

Your email address will not be published. Required fields are marked *