數據庫做爲核心的基礎組件,是須要重點保護的對象。任何一個線上的不慎操做,都有可能給數據庫帶來嚴重的故障,從而給業務形成巨大的損失。爲了不這種損失,通常會在管理上下功夫。好比爲研發人員制定數據庫開發規範;新上線的SQL,須要DBA進行審覈;維護操做須要通過領導審批等等。並且若是但願可以有效地管理這些措施,須要有效的數據庫培訓,還須要DBA細心的進行SQL審覈。不少中小型創業公司,能夠經過設定規範、進行培訓、完善審覈流程來管理數據庫。php
隨着美團點評的業務不斷髮展和壯大,上述措施的實施成本愈來愈高。如何更多的依賴技術手段,來提升效率,愈來愈受到重視。業界已有很多基於MySQL源碼開發的SQL審覈、優化建議等工具,極大的減輕了DBA的SQL審覈負擔。那麼咱們可否繼續擴展MySQL的源碼,來輔助DBA和研發人員來進一步提升效率呢?好比,更全面的SQL優化功能;多維度的慢查詢分析;輔助故障分析等。要實現上述功能,其中最核心的技術之一就是SQL解析。html
SQL解析是一項複雜的技術,通常都是由數據庫廠商來掌握,固然也有公司專門提供SQL解析的API。因爲這幾年MySQL數據庫中間件的興起,須要支持讀寫分離、分庫分表等功能,就必須從SQL中抽出表名、庫名以及相關字段的值。所以像Java語言編寫的Druid,C語言編寫的MaxScale,Go語言編寫的Kingshard等,都會對SQL進行部分解析。而真正把SQL解析技術用於數據庫維護的產品較少,主要有以下幾個:mysql
- 美團點評開源的SQLAdvisor。它基於MySQL原生態詞法解析,結合分析SQL中的where條件、聚合條件、多表Join關係給出索引優化建議。
- 去哪兒開源的Inception。側重於根據內置的規則,對SQL進行審覈。
- 阿里的Cloud DBA。根據官方文檔介紹,其也是提供SQL優化建議和改寫。
上述產品都有很是合適的應用場景,在業界也被普遍使用。可是SQL解析的應用場景遠遠沒有被充分發掘,好比:git
- 基於表粒度的慢查詢報表。好比,一個Schema中包含了屬於不一樣業務線的數據表,那麼從業務線的角度來講,其但願提供表粒度的慢查詢報表。
- 生成SQL特徵。將SQL語句中的值替換成問號,方便SQL歸類。雖然可使用正則表達式實現相同的功能,可是其Bug較多,能夠參考pt-query-digest。好比pt-query-digest中,會把遇到的數字都替換成「?」,致使沒法區別不一樣數字後綴的表。
- 高危操做確認與規避。好比,DBA不當心Drop數據表,而此類操做,目前還無有效的工具進行回滾,尤爲是大表,其後果將是災難性的。
- SQL合法性判斷。爲了安全、審計、控制等方面的緣由,美團點評不會讓研發人員直接操做數據庫,而是提供RDS服務。尤爲是對於數據變動,須要研發人員的上級主管進行業務上的審批。若是研發人員,寫了一條語法錯誤的SQL,而RDS沒法判斷該SQL是否合法,就會形成沒必要要的溝通成本。
所以爲了讓全部有須要的業務都能方便的使用SQL解析功能,咱們認爲應該具備以下特性。github
- 直接暴露SQL解析接口,使用盡可能簡單。好比,輸入SQL,則輸出表名、特徵和優化建議。
- 接口的使用不依賴於特定的語言,不然維護和使用的代價過高。好比,以HTTP等方式提供服務。
千里之行,始於足下,下面我先介紹下SQL的解析原理。正則表達式
SQL解析與優化是屬於編譯器範疇,和C等其餘語言的解析沒有本質的區別。其中分爲,詞法分析、語法和語義分析、優化、執行代碼生成。對應到MySQL的部分,以下圖算法
SQL解析由詞法分析和語法/語義分析兩個部分組成。詞法分析主要是把輸入轉化成一個個Token。其中Token中包含Keyword(也稱symbol)和非Keyword。例如,SQL語句 select username from userinfo,在分析以後,會獲得4個Token,其中有2個Keyword,分別爲select和from:sql
關鍵字 | 非關鍵字 | 關鍵字 | 非關鍵字 |
---|---|---|---|
select | username | from | userinfo |
一般狀況下,詞法分析可使用Flex來生成,可是MySQL並未使用該工具,而是手寫了詞法分析部分(聽說是爲了效率和靈活性,參考此文)。具體代碼在sql/lex.h和sql/sql_lex.cc文件中。數據庫
MySQL中的Keyword定義在sql/lex.h中,以下爲部分Keywordexpress
{ "&&", SYM(AND_AND_SYM)},
{ "<", SYM(LT)},
{ "<=", SYM(LE)},
{ "<>", SYM(NE)},
{ "!=", SYM(NE)},
{ "=", SYM(EQ)},
{ ">", SYM(GT_SYM)},
{ ">=", SYM(GE)},
{ "<<", SYM(SHIFT_LEFT)},
{ ">>", SYM(SHIFT_RIGHT)},
{ "<=>", SYM(EQUAL_SYM)},
{ "ACCESSIBLE", SYM(ACCESSIBLE_SYM)},
{ "ACTION", SYM(ACTION)},
{ "ADD", SYM(ADD)},
{ "AFTER", SYM(AFTER_SYM)},
{ "AGAINST", SYM(AGAINST)},
{ "AGGREGATE", SYM(AGGREGATE_SYM)},
{ "ALL", SYM(ALL)},
複製代碼
詞法分析的核心代碼在sql/sql_lex.c文件中的,MySQLLex→lex_one_Token,有興趣的同窗能夠下載源碼研究。
語法分析就是生成語法樹的過程。這是整個解析過程當中最精華,最複雜的部分,不過這部分MySQL使用了Bison來完成。即便如此,如何設計合適的數據結構以及相關算法,去存儲和遍歷全部的信息,也是值得在這裏研究的。
a)語法分析樹
SQL語句:
select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1
複製代碼
會生成以下語法樹。
對於未接觸過編譯器實現的同窗,確定會好奇如何才能生成這樣的語法樹。其背後的原理都是編譯器的範疇,能夠參考維基百科的一篇文章,以及該連接中的參考書籍。本人也是在學習MySQL源碼過程當中,閱讀了部份內容。因爲編譯器涉及的內容過多,本人精力和時間有限,不作過多探究。從工程的角度來講,學會如何使用Bison去構建語法樹,來解決實際問題,對咱們的工做也許有更大幫助。下面我就以Bison爲基礎,探討該過程。
b)MySQL語法分析樹生成過程
所有的源碼在sql/sql_yacc.yy中,在MySQL5.6中有17K行左右代碼。這裏列出涉及到SQL:
select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1
複製代碼
解析過程的部分代碼摘錄出來。其實有了Bison以後,SQL解析的難度也沒有想象的那麼大。特別是這裏給出瞭解析的脈絡以後。
select /*select語句入口*/:
select_init
{
LEX *lex= Lex;
lex->sql_command= SQLCOM_SELECT;
}
;
select_init:
SELECT_SYM /*select 關鍵字*/ select_init2
| '(' select_paren ')' union_opt
;
select_init2:
select_part2
{
LEX *lex= Lex;
SELECT_LEX * sel= lex->current_select;
if (lex->current_select->set_braces(0))
{
my_parse_error(ER(ER_SYNTAX_ERROR));
MYSQL_YYABORT;
}
if (sel->linkage == UNION_TYPE &&
sel->master_unit()->first_select()->braces)
{
my_parse_error(ER(ER_SYNTAX_ERROR));
MYSQL_YYABORT;
}
}
union_clause
;
select_part2:
{
LEX *lex= Lex;
SELECT_LEX *sel= lex->current_select;
if (sel->linkage != UNION_TYPE)
mysql_init_select(lex);
lex->current_select->parsing_place= SELECT_LIST;
}
select_options select_item_list /*解析列名*/
{
Select->parsing_place= NO_MATTER;
}
select_into select_lock_type
;
select_into:
opt_order_clause opt_limit_clause {}
| into
| select_from /*from 字句*/
| into select_from
| select_from into
;
select_from:
FROM join_table_list /*解析表名*/ where_clause /*where字句*/ group_clause having_clause
opt_order_clause opt_limit_clause procedure_analyse_clause
{
Select->context.table_list=
Select->context.first_name_resolution_table=
Select->table_list.first;
}
| FROM DUAL_SYM where_clause opt_limit_clause
/* oracle compatibility: oracle always requires FROM clause, and DUAL is system table without fields. Is "SELECT 1 FROM DUAL" any better than "SELECT 1" ? Hmmm :) */
;
where_clause:
/* empty */ { Select->where= 0; }
| WHERE
{
Select->parsing_place= IN_WHERE;
}
expr /*各類表達式*/
{
SELECT_LEX *select= Select;
select->where= $3;
select->parsing_place= NO_MATTER;
if ($3)
$3->top_level_item();
}
;
/* all possible expressions */
expr:
| expr and expr %prec AND_SYM
{
/* See comments in rule expr: expr or expr */
Item_cond_and *item1;
Item_cond_and *item3;
if (is_cond_and($1))
{
item1= (Item_cond_and*) $1;
if (is_cond_and($3))
{
item3= (Item_cond_and*) $3;
/* (X1 AND X2) AND (Y1 AND Y2) ==> AND (X1, X2, Y1, Y2) */
item3->add_at_head(item1->argument_list());
$$ = $3;
}
else
{
/* (X1 AND X2) AND Y ==> AND (X1, X2, Y) */
item1->add($3);
$$ = $1;
}
}
else if (is_cond_and($3))
{
item3= (Item_cond_and*) $3;
/* X AND (Y1 AND Y2) ==> AND (X, Y1, Y2) */
item3->add_at_head($1);
$$ = $3;
}
else
{
/* X AND Y */
$$ = new (YYTHD->mem_root) Item_cond_and($1, $3);
if ($$ == NULL)
MYSQL_YYABORT;
}
}
複製代碼
在你們瀏覽上述代碼的過程,會發現Bison中嵌入了C++的代碼。經過C++代碼,把解析到的信息存儲到相關對象中。例如表信息會存儲到TABLE_LIST中,order_list存儲order by子句裏的信息,where字句存儲在Item中。有了這些信息,再輔助以相應的算法就能夠對SQL進行更進一步的處理了。
c)核心數據結構及其關係
在SQL解析中,最核心的結構是SELECT_LEX,其定義在sql/sql_lex.h中。下面僅列出與上述例子相關的部分。
上面圖示中,列名username、ismale存儲在item_list中,表名存儲在table_list中,條件存儲在where中。其中以where條件中的Item層次結構最深,表達也較爲複雜,以下圖所示。
爲了更深刻的瞭解SQL解析器,這裏給出2個應用SQL解析的例子。
無用條件去除屬於優化器的邏輯優化範疇,能夠僅僅根據SQL自己以及表結構便可完成,其優化的狀況也是較多的,代碼在sql/sql_optimizer.cc文件中的remove_eq_conds函數。爲了不過於繁瑣的描述,以及大段代碼的粘貼,這裏經過圖來分析如下四種狀況。
a)1=1 and (m > 3 and n > 4)
b)1=2 and (m > 3 and n > 4)
c)1=1 or (m > 3 and n > 4)
d)1=2 or (m > 3 and n > 4)
若是對其代碼實現有興趣的同窗,須要對MySQL中的一個重要數據結構Item類有所瞭解。由於其比較複雜,因此MySQL官方文檔,專門介紹了Item類。阿里的MySQL小組,也有相似的文章。如需更詳細的瞭解,就須要去查看源碼中sql/item_*等文件。
爲了確保數據庫,這一系統基礎組件穩定、高效運行,業界有不少輔助系統。好比慢查詢系統、中間件系統。這些系統採集、收到SQL以後,須要對SQL進行歸類,以便統計信息或者應用相關策略。歸類時,一般須要獲取SQL特徵。好比SQL:
select username, ismale from userinfo where age > 20 and level > 5;``` SQL特徵爲: ```sql
select username, ismale from userinfo where age > ? and level > ?
複製代碼
業界著名的慢查詢分析工具pt-query-digest,經過正則表達式實現這個功能可是這類處理辦法Bug較多。接下來就介紹如何使用SQL解析,完成SQL特徵的生成。
SQL特徵生成分兩部分組成。
a) 生成Token數組
b) 根據Token數組,生成SQL特徵
首先回顧在詞法解析章節,咱們介紹了SQL中的關鍵字,而且每一個關鍵字都有一個16位的整數對應,而非關鍵字統一用ident表示,其也對應了一個16位整數。以下表:
標識 | select | from | where | > | ? | and | ident |
---|---|---|---|---|---|---|---|
整數 | 728 | 448 | 878 | 463 | 893 | 272 | 476 |
將一個SQL轉換成特徵的過程:
原SQL | select | username | from | userinfo | where | age | > | 20 |
---|---|---|---|---|---|---|---|---|
SQL特徵 | select | ident:length:value | from | ident:length:value | where | ident:length:value | > | ? |
在SQL解析過程當中,能夠很方便的完成Token數組的生成。而一旦完成Token數組的生成,就能夠很簡單的完成SQL特徵的生成。SQL特徵被普遍用於各個系統中,好比pt-query-digest須要根據特徵對SQL歸類,然而其基於正則表達式的實現有諸多bug。下面列舉幾個已知bug。
原始SQL | pt-query-digest生成的特徵 | SQL解析器生成的特徵 |
---|---|---|
select * from email_template2 where id = 1 | select * from mail_template? where id = ? | select * from email_template2 where id = ? |
REPLACE INTO a VALUES('INSERT INTO foo VALUES (1),(2)') | replace into a values('insert into foo values(?+) | replace into a values (?) |
所以能夠看出SQL解析的優點是很明顯的。
最近,在對SQL解析器和優化器探索的過程當中,從一開始的茫然無措到有章可循,也總結了一些心得體會,在這裏跟你們分享一下。
首先,閱讀相關圖書書籍。圖書能給咱們系統認識解析器和優化器的角度。可是針對MySQL的此類圖書市面上不多,目前中文做品能夠看一看《數據庫查詢優化器的藝術:原理解析與SQL性能優化》。
其次,要閱讀源碼,可是最好以某個版本爲基礎,好比MySQL5.6.23,由於SQL解析、優化部分的代碼在不斷變化。尤爲是在跨越大的版本時,改動力度大。
再次,多使用GDB調試,驗證本身的猜想,檢驗閱讀質量。
最後,須要寫相關代碼驗證,只有寫出來了才能算真正的掌握。
廣友,美團點評到店綜合事業羣MySQL DBA專家,2012年畢業於中國科學技術大學,2017年加入美團點評,長期致力於MySQL及周邊工具的研究。
金龍,2014年加入美團點評,主要從事相關的數據庫運維、高可用和相關的運維平臺建設。對運維高可用與架構相關感興趣的同窗能夠關注我的微信公衆號「本身的設計師」,按期推送運維相關原創內容。
邢帆,美團點評到店綜合事業羣MySQL DBA,2017年研究生畢業後加入美團點評,目前已經對MySQL運維有必定經驗,並編寫了一些自動化腳本。
美團點評DBA團隊招聘各種DBA人才,base北京上海都可。咱們致力於爲公司提供穩定、可靠、高效的在線存儲服務,打造業界領先的數據庫團隊。這裏有基於Redis Cluster構建的大規模分佈式緩存系統Squirrel,也有基於Tair進行大刀闊斧改進的分佈式KV存儲系統Cellar,還有數千各種架構的MySQL實例,天天提供萬億級的OLTP訪問請求。真正的海量、分佈式、高併發環境。歡迎各位朋友推薦或自薦至jinlong.cai#dianping.com。
若是對咱們團隊感興趣,能夠關注咱們的專欄。