MySQL SQL 解析包含詞法分析與語法分析。不同於通用的 Flex/Bison 組合,MySQL 的詞法分析器是**手工編寫的高優化 C 代碼**,核心在於關鍵字查找的 Trie 樹(前綴樹)實現。本文深入源碼級解析 gen_lex_hash 生成過程與 get_hash_symbol 查找邏輯。
MYSQLparse() (由 Bison 生成 sql_yacc.yy)MYSQLlex() (手工編寫 sql_lex.cc)sql_lex.cc:
find_keyword() -> get_hash_symbol()
is_keyword() -> get_hash_symbol()
is_lex_native_function() -> get_hash_symbol()
lex_hash.h (生成):
get_hash_symbol() -> sql_functions_map / symbols_map
sql/lex.h 的 SYMBOL symbols[])symbols_map[] (無指針、緊湊佈局)sql/lex_hash.h 供編譯期包含# sql/CMakeFiles/sql.dir/build.make
sql/lex_hash.h: sql/gen_lex_hash
./gen_lex_hash > lex_hash.h
gen_lex_hash 為編譯期工具,運行於構建機,產出的 lex_hash.h 隨源碼分發,運行期無依賴。
+-----------+-+-+-+
| len |1|2|3| <-- 按關鍵字長度分層
+-----------+-+-+-+
|first_char |0|0|a| <-- 該長度層首字符最小值
|last_char |0|0|d| <-- 該長度層首字符最大值
|link |0|0|+| <-- 指向下一層節點陣列偏移
|
V
+----------+-+-+-+--+
| 1 char |a|b|c|d | <-- 長度=1 時的首字符節點
+----------+-+-+-+--+
|first_char|d|0|0|0 |
|last_char |n|0|0|-1|
|link |+|0|0|+ | <-- 指向長度=2 層
|
V
+----------+--+-+-+-+-+-+-+-+-+-+--+
| 2 char |d |e|f|j|h|i|j|k|l|m|n | <-- 長度=2 時的次字符節點
+----------+--+-+-+-+-+-+-+-+-+-+--+
|first_char|0 |0|0|0|0|0|0|0|0|0|0 |
|last_char |-1|0|0|0|0|0|0|0|0|0|-1|
|link |+ |0|0|0|0|0|0|0|0|0|+ |
| | | |
V V V
symbols[0]("ADD") symbols[1]("AND") ...
symbols_map[] 結構每個節點佔 4 位元組:
struct Node {
uchar first_char; // 該層首字符最小值
uchar last_char; // 該層首字符最大值
uchar link; // 下一層偏移 (0=無)
uchar iresult; // 索引: 0=未找到, >0 對應 symbols[] 索引
};
| 關鍵字集合 | 生成的 symbols_map 片段 | 說明 |
|---|---|---|
{"&&"} | 0,0,1,0, 0,0,0,0 | 長度=2,首字符 '&',找到 symbols[0] |
{"&&", "<"} | 0,0,1,0, 0,0,0,0 | 長度=1 新增 '<',iresult=1 指向 symbols[1] |
{"&&", "<", "<="} | 0,0,1,0, '&','<',2,0, 0,0,0,0, 0,0,3,0... | 長度=2 層擴展,'&' 指向 "&&",'<' 指向下一層 '=' |
{"&&", "<", "<=", "<>"} | ..., '=', '>', 25, 0, 0,0,2,0, 0,0,3,0... | 長度=3 層新增 '=' 和 '>',分別指向 symbols[2]、symbols[3] |
function get_hash_symbol(keyword, length):
node = root[length] // 根據長度進入對應層
if not node: return NOT_FOUND
for i = 0 to length-1:
c = keyword[i]
if c < node.first_char or c > node.last_char:
return NOT_FOUND
offset = (c - node.first_char) * 4
node = symbols_map[node.link + offset]
if i == length-1: // 最後一字符
if node.iresult > 0:
return symbols[node.iresult - 1] // 找到
else:
return NOT_FOUND
if node.link == 0: // 中間無下一層
return NOT_FOUND
return NOT_FOUND
陣列表示中,first_char~last_char 範圍外的字符位置佔據空間但無實際用途 (如示例中大量 0,0,4,0 重複)。
base[] + check[] 壓縮稀疏空間,Aho-Corasick 自動機常用gen_lex_hash 邏輯基本不變,但 symbols[] 規模增長KEYWORDS 系統表 (SELECT * FROM INFORMATION_SCHEMA.KEYWORDS) 可動態查詢保留/非保留關鍵字gen_lex_hash 納入構建流程,保證源碼與查找表同步MySQL 手工實現的詞法分析器展示了**領域特定優化**的極致:針對固定關鍵字集合,構建編譯期生成、運行期只讀的 Trie 樹陣列,實現確定性 O(L) 查找,零鎖、零衝突、緩存友好。這是資料庫核心模組「為性能重造輪子」的經典案例。