Sharding-JDBC 原始碼之 SQL 解析
基於 SpringBoot2.4 快速搭建一個 Demo,主要 jar 包依賴版本分別是:
- io.shardingsphere.sharding-jdbc-spring-boot-starter:3.0.0.M1
- com.alibaba.druid-spring-boot-starter:1.1.17
- org.mybatis.spring.boot.mybatis-spring-boot-starter:2.1.4
在執行 SQL 時,Sharding-JDBC 需要根據啟動時獲取到的資料來源、路由等配置檔案獲取真正執行的 SQL, 那在獲取真正執行的 語句之前還需要對 SQL 進行解析、路由,今天我們來看下 Sharding-JDBC 是如何解析 SQL 語句的。
本文以查詢語句為例進行分析,資料來源及分庫分表配置和之前保持一致。
從原始碼看,SQL 解析工作主要在 Sharding-JDBC 原始碼之 SQL 解析 的 parsing 包中:
- cache 包:快取解析過 SQL 語句;
- lexer 包:詞法分析,封裝不同方言(Mysql、Oracle 等)的解析器,使用
LexerEngine
完成詞法分析; - parser 包:SQL 解析引擎,根據 SQL 語句的型別完成解析工作。
在看具體執行流程前,先了解下 parsing 封裝的集中 SQL 語句型別:
- dal:資料訪問語言(Data Access Language) 例: use、desc、describe、show
- ddl:資料定義語言(Data Definition Language) 例:create、alter、drop、truncate
- dml:資料操作語言(Data Manipulation Language) 例: insert、update、delete
- dql:資料查詢語言(Data Query Language) 例: select
- tcl:事務控制語言(Transaction Control Language) 例: set、commit、rollback、savepoint、begin。
在執行語句時,需要進行路由,而進行路由,需要對 SQL 進行解析,下面看下解析的具體執行流程:
獲取 SQL 解析引擎
由於我們使用的是標準分片策略,所以獲取到的解析引擎是 SQLParsingEngine,而該引擎中的關鍵方法是 parse:
public final class ParsingResultCache {
// 省略部分程式碼
// 邏輯 SQL 快取
private volatile Map<String, SQLStatement> cache = new WeakHashMap<>(65535, 1);
// 省略部分程式碼
}
public final class SQLParsingEngine {
......
public SQLStatement parse(final boolean useCache) {
// 從快取中獲取解析結果
Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(useCache);
// 快取中存在則直接獲取返回解析結果
if (cachedSQLStatement.isPresent()) {
return cachedSQLStatement.get();
}
// 根據資料庫型別獲取具體方言詞法分析器
LexerEngine lexerEngine = LexerEngineFactory.newInstance(dbType, sql);
lexerEngine.nextToken();
// 根據邏輯 SQL 型別獲取具體語句解析器,並解析語言
SQLStatement result = SQLParserFactory.newInstance(dbType, lexerEngine.getCurrentToken().getType(), shardingRule, lexerEngine, shardingMetaData).parse();
// 將解析結果快取到 cache 物件中
if (useCache) {
ParsingResultCache.getInstance().put(sql, result);
}
return result;
}
// 從快取中獲取解析結果
private Optional<SQLStatement> getSQLStatementFromCache(final boolean useCache) {
return useCache ? Optional.fromNullable(ParsingResultCache.getInstance().getSQLStatement(sql)) : Optional.<SQLStatement>absent();
}
}
- ParsingResultCache 類中的 cache 物件主要用來快取解析過的邏輯 SQL,避免重複解析。同時 cache 使用的是 WeakHashMap,這樣可以保證不用手動釋放引用且保證記憶體不被過度使用,因為弱引用物件只能存活到下一次 GC 之前,
快取的 key 形如:select goods_name from t_goods where goods_id=?
。 - 本例中,是 select 查詢語句,所以獲取的是
MySQLSelectParser
,執行該解析器的parseInternal
方法,其他型別的語句根據解析器的不同,在執行parse
方法時進入具體的實現類中。
解析 SQL
解析邏輯 SQL,將解析結果封裝為 SelectStatement
物件
public MySQLSelectParser(final ShardingRule shardingRule, final LexerEngine lexerEngine, final ShardingMetaData shardingMetaData) {
// 1. 建立 DQL 門面解析器
super(shardingRule, lexerEngine, new MySQLSelectClauseParserFacade(shardingRule, lexerEngine), shardingMetaData);
selectOptionClauseParser = new MySQLSelectOptionClauseParser(lexerEngine);
limitClauseParser = new MySQLLimitClauseParser(lexerEngine);
}
/**
* 2. 解析具體邏輯 SQL
*/
protected void parseInternal(final SelectStatement selectStatement) {
parseDistinct();
parseSelectOption();
// 解析待查詢的列,獲取資料庫欄位名和欄位別名
parseSelectList(selectStatement, getItems());
// 解析獲取邏輯表
parseFrom(selectStatement);
// 獲取查詢條件
parseWhere(getShardingRule(), selectStatement, getItems());
parseGroupBy(selectStatement);
parseHaving();
parseOrderBy(selectStatement);
parseLimit(selectStatement);
parseSelectRest();
}
- 建立
MySQLSelectClauseParserFacade
物件,初始化 DQL 語句門面解析器,為下面parseInternal
做準備; - 解析具體邏輯 SQL,將語句按照 token 型別封裝為
SelectStatement
物件,為後面 SQL 改寫做準備。
總結
- 具體的詞法分析並沒有展開,主要是根據資料庫型別獲取對應的詞法解析器,本文以 Mysql 為例,因此獲取的是
MySQLLexer
,接下來詞法分析引擎LexerEngine
獲取到第一個有效的token
,並根據此 token 獲取 SQL 解析器,本例中邏輯 SQL 第一個有效的 token 是select
,所以獲得的解析器是MySQLSelectParser
; - 整體上,語句解析過程還是很簡單的,就是將邏輯 SQL 按照 token 型別解析為
SQLStatement
,為後面 SQL 路由改寫做準備。不過,詞法分析還是略複雜,有興趣可自行 debug 跟蹤學習。
相關文章
- 原始碼解析MyBatis Sharding-Jdbc SQL語句執行流程詳解(文末有流程圖)原始碼MyBatisJDBCSQL流程圖
- Sharding-JDBC 原始碼之啟動流程分析JDBC原始碼
- Shading – jdbc 原始碼分析(三) – sql 解析之 SelectJDBC原始碼SQL
- Mybatis原始碼解析之執行SQL語句MyBatis原始碼SQL
- Sharding-JDBC原始碼解析與vivo的定製開發JDBC原始碼
- MySQL核心原始碼解讀-SQL解析之解析器淺析MySql原始碼
- Dubbo原始碼解析之SPI原始碼
- Vue原始碼解析之nextTickVue原始碼
- Flutter之Navigator原始碼解析Flutter原始碼
- Vue原始碼解析之parseVue原始碼
- jQuery原始碼解析之position()jQuery原始碼
- jQuery原始碼解析之clone()jQuery原始碼
- LevelDB 原始碼解析之 Arena原始碼
- Spring原始碼之IOC(一)BeanDefinition原始碼解析Spring原始碼Bean
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- MySQL核心原始碼解讀-SQL解析一MySql原始碼
- [原始碼解析] GroupReduce,GroupCombine 和 Flink SQL group by原始碼SQL
- LevelDB 原始碼解析之 Varint 編碼原始碼
- spring 原始碼解析之開篇Spring原始碼
- Java集合之Hashtable原始碼解析Java原始碼
- jQuery原始碼解析之replaceWith()/unwrap()jQuery原始碼
- Java集合之ArrayList原始碼解析Java原始碼
- JDK原始碼解析系列之objectJDK原始碼Object
- Drill-on-YARN之原始碼解析Yarn原始碼
- @angular/forms 原始碼解析之 ValidatorsAngularORM原始碼
- Java集合之LinkedList原始碼解析Java原始碼
- Spring原始碼解析之BeanFactoryPostProcessor(一)Spring原始碼Bean
- Spring原始碼解析之ConfigurationClassPostProcessor(一)Spring原始碼
- Spring原始碼解析之BeanFactoryPostProcessor(三)Spring原始碼Bean
- Spring原始碼解析之ConfigurationClassPostProcessor(二)Spring原始碼
- Spring原始碼解析之ConfigurationClassPostProcessor(三)Spring原始碼
- 【spring原始碼系列】之【xml解析】Spring原始碼XML
- Spring原始碼解析之BeanFactoryPostProcessor(二)Spring原始碼Bean
- Spark SQL原始碼解析(四)Optimization和Physical Planning階段解析SparkSQL原始碼
- Shading-jdbc原始碼分析-sql詞法解析JDBC原始碼SQL
- 原始碼解析丨一次慢SQL排查原始碼SQL
- Spark 原始碼系列(九)Spark SQL 初體驗之解析過程詳解Spark原始碼SQL
- 資料庫中介軟體 Sharding-JDBC 原始碼分析 —— 事務(一)之BED資料庫JDBC原始碼