Apache頂級專案ShardingSphere — SQL Parser的設計與實現
導語:SQL作為現代計算機行業的資料處理事實標準,是目前最重要的資料處理介面之一,從傳統的DBMS(如MySQL、Oracle),到主流的計算框架(如spark,flink)都提供了SQL的解析引擎,因此想對sql進行精細化的操作,一定離不開SQL Parser。Apache ShardingSphere 是一套開源的分散式資料庫中介軟體解決方案組成的生態圈,需要對SQL進行精細化的操作,如改寫,加密等,因此也實現了SQL Parser,並提供獨立的Parser引擎。
先來認識一下傳統資料庫中一條SQL處理流程是怎樣的,接受網路包-》資料庫協議解析網路包的到sql-》SQL語法解析為抽象語法樹-》把語法樹轉換成關係代數表示式樹(邏輯執行計劃)-》再轉換成物理運算元樹(物理執行計劃)-》遍歷物理運算元樹執行相應運算元的實現獲取資料並返回。
一、工作原理
SQL Parser 的功能是把一條SQL解析為抽象語法樹(AST),SQL Parser需要編譯原理相關的知識,簡單介紹一下。
SQL Parser 包含詞法解析(Lexer)和語法解析(Parser),詞法解析的作用是把一個sql 分割成一個一個不可分割的單元,例子:
原始sql: select id,name from table1 where name=“xxx”;
詞法解析器輸入是原始sql,並且暴露一個介面nextToken(),每次呼叫nextToken()都會返回一個Token(表示上面所說的不可分割的元素),虛擬碼如下:
String originSql = "select id,name from table1 where name='xxx'";
Lexer lexer = new Lexer(originSql);
while(!lexer._hitEOF){// 判斷是否結束
System.out.printLn(lexer.nextToken())
}
// 輸出如下:
select
id
,
name
from
table1
where
name
=
'xxx'
語法分析器的作用是使用Lexer的輸出(呼叫nextToken()),構造出AST,以上面的sql為例,解析器得出的語法樹如下:
1、Lexer(詞法分析)原理
Lexer 也稱為分詞,從左向右掃描SQL,將其分割成一個個的toke(不可分割的,具有獨立意義的單元,類似英語中的單詞)。
Lexer的實現一般都是構造DFA(確定性有限狀態自動機)來實現的,以一個例子說明。
狀態轉移圖如下,這是一個能夠識別識別符號,數字和一般運算子的詞法解析器。
程式碼實現可以使用傳統的while case的模板實現,虛擬碼如下:
Class Token{...}
Enum State {
BEGIN,OPERATER,IDENTIFIER,NUMBER
}
Class Lexer{
String input = "...";
State state = BEGIN;
int index = 0;
Token nextToken() {
while(index < input.length) {
Char char = input.charAt(index);
switch (state) {
case BEGIN:
switch (char){
case a-zA-Z_ :
index++;
state = IDENTIFIER;
case +-*/ :
state = BEGIN
return Token(OPERATER)
case 0-9:
state = NUMBER;
index++;
default:
return null;
}
case IDENTIFIER:
switch (char){
case a-zA-Z_0-9 :
index++;
state = IDENTIFIER;
default :
state = BEGIN;
index--;
return Token(IDENTIFIER)
}
case NUMBER:
switch (char){
case 0-9 :
index++;
state = NUMBER;
default :
state = BEGIN;
index--;
return Token(NUMBER)
}
default:
return null;
}
}
}
}
2、Parser(語法解析) 原理
Parser階段有兩種型別方法來實現,一種是自頂向下分析法,另一種是自底向上分析法,簡單介紹一下兩種型別分析法的處理思路。
首先給出上下文無關語法和相關術語的定義:
- 終結符集合 T (terminal set)
一個有限集合,其元素稱為 終結符(terminal) - 非終結符集合 N (non-terminal set)
一個有限集合,與 T 無公共元素,其元素稱為 非終結符(non-terminal) - 符號集合 V (alphabet)
T 和 N 的並集,其元素稱為符號(symbol) 。因此終結符和非終結符都是符號。符號可用字母:A, B, C, X, Y, Z, a, b, c 等表示。 - 符號串(a string of symbols)
一串符號,如 X1 X2 … Xn 。只有終結符的符號串稱為 句子(sentence)。空串 (不含任何符號)也是一個符號串,用 ε 表示。符號串一般用小寫字母 u, v, w 表示。 - 產生式(production)
一個描述符號串如何轉換的規則。對於上下文字無關語法,其固定形式為:A -> u ,其中 A 為非終結符, u 為一個符號串。 - 產生式集合 P (production set)
一個由有限個產生式組成的集合 - 展開(expand)
一個動作:將一個產生式 A -> u 應用到一個含有 A 的符號串 vAw 上,用 u 代替該符號串中的 A ,得到一個新的符號串 vuw。 - 摺疊(reduce)
一個動作:將一個產生式 A -> u 應用到一個含有 u 的符號串 vuw 上,用 A 代替該符號串中的 u ,得到一個新的符號串 vAw。 - 起始符號 S (start symbol)
N 中的一個特定的元素 - 推導(derivate)
一個過程:從一個符號串 u 開始,應用一系列的產生式,展開到另一個的符號串 v。若 v 可以由 u 推導得到,則可寫成:u => v 。 - 上下文字無關語法 G (context-free grammar, CFG)
一個 4 元組:(T, N, P, S) ,其中 T 為終結符集合, N 為非終結符集合, P 為產生式集合, S 為起始符號。一個句子如果能從語法 G 的 S 推導得到,可以直接稱此句子由語法 G 推導得到,也可稱此句子符合這個語法,或者說此句子屬於 G 語言。G 語言( G language) 就是語法 G 推匯出來的所有句子的集合,有時也用 G 代表這個集合。 - 解析(parse)
也稱為分析,是一個過程:給定一個句子 s 和語法 G ,判斷 s 是否屬於 G ,如果是,則找出從起始符號推導得到 s 的全過程。推導過程中的任何符號串(包括起始符號和最終的句子)都稱為 中間句子(working string)。
2.1 自頂向下分析法 LL(1)
LL(1) 是自頂向下分析法的一種,第一個L代表從左向右掃描待解析文字,第二個L代表從左向右展開,(1)代表每次讀取一個Token。
以簡單表示式為例子:
// 語法規則, 整個是一個產生式,左邊expr為非終結符,NUM是一
//個Token,為終結符
expr: NUM | NUM expr
// 待解析文字
1 2 23 45
解析過程如下:
我們的目標是將起始符號expr展開成句子 1 2 23 45
- 對比expr和NUM(1),只能選擇expr -> NUM expr,才可以和NUM(1)匹配,展開後得NUM expr
- 忽略已匹配的NUM(1), 再次讀入NUM(2),只能選擇expr -> NUM expr,展開後得NUM expr
- 忽略已匹配的NUM(2),再次讀入NUM(23),只能選擇expr -> NUM expr,展開後得NUM expr
- 忽略已匹配的NUM(23),再次讀入NUM(45),只能選擇expr -> NUM,展開後得NUM
- 得到最終句子 NUM(1)NUM(2) NUM(23) NUM(45) 可接受,解析完成
注意點:為什麼1,2,3只能選擇expr -> NUM expr, Lexer中會有介面判斷是否得到末尾,ShardingSphere中介面是lexer._hitEOF。
2.1 自底向上分析法 LR(1)
自底向上分析的順序和自頂向下分析的順序相反,從給定的句子開始,不斷的挑選出合適的產生式,將中間句子中的子串摺疊為非終結符,最終摺疊到起始符號。
LR(1) 是自底向上分析法的一種,第一個L代表從左向右掃描待解析文字,第二個R代表從右向坐摺疊,(1)代表每次讀取一個Token。
以簡單表示式為例:
// 語法規則, 整個是一個產生式,左邊expr為非終結符,NUM是一
//個Token,為終結符
expr: NUM | expr NUM
// 待解析文字
1 2 23 45
解析過程是如下:
我們的目標是把 1 2 23 45 摺疊為expr
- 讀入NUM(1),發現只能選擇expr -> NUM, 摺疊得expr
- 讀入NUM(2),只能選擇expr -> expr NUM,摺疊得expr
- 讀入NUM(23),只能選擇expr -> expr NUM, 摺疊得expr
- 讀入NUM(45),只能選擇expr-> expr NUM, 摺疊得expr 可接受,解析完成
二、ShardingSphere Parser 實現
實現Parser的方式一般分為兩種,一種是寫程式碼實現狀態機來進行解析,另一種是通過解析器生成器根據定義的語法規則生成解析器,ShardingSphere使用第二種方式,這是由於衡量了效能,擴充套件性和容易維護因素最終決定的。
以ShardingSphere中的MySQL 解析引擎為例,模組shardingsphere-sql-parser-mysql,語法定義路徑src/main/antlr。
功能點:
- 提供獨立的SQL解析引擎
- 可以方便的對語法規則進行擴充和修改
- 提供SQL 變數引數化功能
- 提供SQL 格式化功能
- 支援多種方言
資料庫 | 支援狀態 |
---|---|
MySQL | 支援,完善 |
PostgreSQL | 支援,完善 |
SQLServer | 支援 |
Oracle | 支援 |
SQL92 | 支援 |
使用方法:
//maven 依賴
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-sql-parser-engine</artifactId>
<version>${project.version}</version>
</dependency>
// 根據需要引入指定方言的解析模組(以MySQL為例),可以新增所有支援的方言,也可以只新增使用到的
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-sql-parser-mysql</artifactId>
<version>${project.version}</version>
</dependency>
//獲取語法樹
/**
* databaseType type:String 可能值 MySQL,Oracle,PostgreSQL,SQL92,SQLServer
* sql type:String 解析的SQL
* useCache type:boolean 是否使用快取
* @return parse tree
*/
ParseTree tree = new SQLParserEngine(databaseType).parse(sql, useCache);
// 獲取SQLStatement
/**
* databaseType type:String 可能值 MySQL,Oracle,PostgreSQL,SQL92,SQLServer
* useCache type:boolean 是否使用快取
* @return SQLStatement
*/
ParseTree tree = new SQLParserEngine(databaseType).parse(sql, useCache);
SQLVisitorEngine sqlVisitorEngine = new SQLVisitorEngine(databaseType, "STATEMENT");
SQLStatement sqlStatement = sqlVisitorEngine.visit(tree);
三、常見解析器
1、MySQL Parser
詞法解析通過手寫程式碼的方式實現,語法解析通過定義語法規則,使用bison生成語法解析程式碼。
2、PostgreSQL Parser
通過定義語法規則,使用flex/bison生成詞法,語法解析程式碼。
3、TIDB Parser(能夠單獨使用, golang)
詞法解析通過手寫程式碼的方式實現,語法解析通過定義語法規則,使用goyacc生成語法解析程式碼。
4、ShardingSphere Parser(能夠單獨使用, 多種目標語言c,c++,java, golang,python)
- 通過定義語法規則,使用ANTLR生成詞法,語法解析程式碼
- 可以方便自定義語法
- 提供快取機制
- SQL 格式化,SQL引數化
- 支援多種DB: Mysql, PostgreSQL, SQLServer, Oracle, SQL92
- 支援自定義visitor
5、alibaba druid(能夠單獨使用,java)
- 通過程式碼的方式實現詞法解析器和語法解析器
- 支援多種DB: Mysql, PostgreSQL, SQLServer, Oracle, odps, db2, hive, SQL92
- SQL 格式化,SQL引數化
- 支援自定義visitor
6、Jsqlparser(能夠單獨使用, java)
- 通過javacc定義語法解析規則實現
- 不侷限於某一個DB
注意:
- 基本都是通過定義語法來實現的,詞法解析都是通過定義正則語言,構造有限狀態自動機實現,區別是LL(自頂向下)語法和LR(自底向上)語法。
- antlr使用的是LL(自頂向下)的語法規則 改進的LL(*)演算法,Bison和goyacc使用LR(自底向上)的語法規則,LALR演算法,Jsqlparser是LL(k), druid目測類似LL(k)。
- LR相比LL表達能力更強,典型的例子是antlr不支援相互左遞迴,bison支援。
四、AST(語法樹)應用
- 轉化為邏輯執行計劃,邏輯執行計劃再轉換為物理執行計劃,物理執行計劃用於儲存引擎具體執行。
eg: select * from table1, table2 where table1.id=1 轉化為邏輯執行計劃為:
- 通過遍歷語法樹,對SQL進行格式化,引數化等
參考文獻
[1] LL(*):https://www.antlr.org/papers/LL-star-PLDI11.pdf
[2] LALR:https://suif.stanford.edu/dragonbook/lecture-notes/Stanford-CS143/11-LALR-Parsing.pdf;http://www.cs.ecu.edu/karl/5220/spr16/Notes/Bottom-up/lalr.html
[3] TiDB優化器設計:https://pingcap.com/blog-cn/tidb-cascades-planner
[4] MySQL優化器設計:https://dev.mysql.com/doc/refman/8.0/en/cost-model.html
本文作者
陸敬尚,京東數科軟體工程師,Apache ShardingSphere Committer,熱愛開源,現專注於ShardingSphere 的開源建設和開發工作。
招聘資訊
京東數科長期招聘Apache ShardingSphere的開源工程師(點選閱讀原文可查詢崗位詳情),歡迎優秀的開源人才加入我們,共同打造出色的Apache頂級專案!簡歷投遞郵箱:zhangliang@apache.org。
往期好文推薦:
2020 ICDM 知識圖譜競賽獲獎技術方案
一文讀懂聯邦學習的前世今生(建議收藏)
突破DevOps瓶頸:京東數科自動化測試平臺建設實踐
京東數科七層負載 | HTTPS硬體加速 (Freescale加速卡篇)
京東數科mPaaS:深度解讀京東金融App(Android)的秒開優化實踐
相關文章
- SQL 居然還能在 Apache ShardingSphere 上實現這些功能?SQLApache
- 參與 Apache 頂級開源專案的 N 種方式,Apache Dubbo Samples SIG 成立!Apache
- 我給 Apache 頂級專案提了個 BugApache
- 正式畢業!Apache Kyuubi 成為 Apache 基金會頂級專案!Apache
- 官宣:計算中介軟體 Apache Linkis 正式畢業成為 Apache 頂級專案Apache
- 盤點 35 個 Apache 頂級專案,我拜服了…Apache
- Dubbo 畢業,成為 Apache 基金會頂級專案Apache
- Cobar SQL審計的設計與實現SQL
- Apache ShardingSphere 如何實現分散式事務Apache分散式
- 我給Apache頂級專案貢獻了點原始碼。Apache原始碼
- 攜程開源專案——Apollo的設計與實現
- 🎉我是如何從零到成為 Apache 頂級專案的 CommitterApacheMIT
- Apache ShardingSphere HINT 實用指南Apache
- Apache頂級開源專案——機器學習庫MADlib簡介與應用例項Apache機器學習
- 官宣!ASF官方正式宣佈Apache Hudi成為頂級專案Apache
- TiDB 原始碼閱讀系列文章(五)TiDB SQL Parser 的實現TiDB原始碼SQL
- Apache ShardingSphere 5.0.0 核心優化及升級指南Apache優化
- Apache InLong畢業成為頂級專案,具備百萬億級資料流處理能力Apache
- Java中的多級快取設計與實現Java快取
- RocketMQ 多級儲存設計與實現MQ
- Doris 畢業成為 Apache 頂級專案,獨家專訪百度 PALO 團隊Apache
- 超級賬本-頂級專案介紹
- Apache ShardingSphere 後設資料載入剖析Apache
- 億級流量實驗平臺設計與實現
- 輕量級工作流引擎的設計與實現
- 基於Vue3+TS的Monorepo前端專案架構設計與實現VueMono前端架構
- 程式設計方式實現MySQL批量匯入sql檔案程式設計MySql
- 從NewSQL的角度看Apache ShardingSphereSQLApache
- 1800 美金?Apache ShardingSphere 帶薪遠端實習招募啦!| 2021 Google 程式設計之夏ApacheGo程式設計
- 專案中多級快取設計實踐總結快取
- 頂級開源專案 Sentry 20.x JS-SDK 設計藝術(概述篇)JS
- Titan 的設計與實現
- LFU 的設計與實現
- 低程式碼之光!輕量級 GUI 的設計與實現GUI
- 輕量級分散式鎖的設計原理分析與實現分散式
- Rust程式設計與專案實戰-結構體Rust程式設計結構體
- 【專案記錄】個人主頁設計和實現
- 頂級開源專案 Sentry 20.x JS-SDK 設計藝術(Unified API篇)JSNifiAPI