平時我們在使用數(shù)據(jù)庫時往弓,看到的通常是一個整體蓄氧。比如你有一個最簡單的表,表里只有一個ID字段撇寞,在執(zhí)行下面的查詢語句時:
mysql > select * from T where ID = 1;
我們只知道輸入是一條SQL語句堂氯,輸出是MySQL返回的結(jié)果集,卻不知道這條語句在MySQL內(nèi)部的執(zhí)行過程啤握。本篇文章主要是拆解MySQL局扶,告訴你MySQL內(nèi)部都有哪些零件,分別有何作用畜埋,以此讓你加深對MySQL的理解畴蒲。
MySQL基本架構(gòu)
下圖是一張MySQL的基本架構(gòu)示意圖大體來講模燥,MySQL由server層和存儲引擎層兩部分組成。
Server 層包括連接器么翰、查詢緩存辽旋、分析器、優(yōu)化器码耐、執(zhí)行器等溶其,涵蓋 MySQL 的大多數(shù)核心服務(wù)功能,以及所有的內(nèi)置函數(shù)(如日期束铭、時間厢绝、數(shù)學(xué)和加密函數(shù)等),所有跨存儲引擎的功能都在這一層實現(xiàn)埠褪,比如存儲過程挤庇、觸發(fā)器、視圖等渴语。
而存儲引擎層負(fù)責(zé)數(shù)據(jù)的存儲和提取昆咽。其架構(gòu)模式是插件式的牙甫,支持 InnoDB窟哺、MyISAM技肩、Memory 等多個存儲引擎。
接下來旋奢,我以開頭那條SQL語句然痊,走一遍整個執(zhí)行流程,看看每個組件的作用爽丹。
連接器
第一步辛蚊,你會先連接到這個數(shù)據(jù)庫上,這時候接待你的就是連接器初澎。連接器負(fù)責(zé)跟客戶端建立連接虑凛、獲取權(quán)限桑谍、維持和管理連接。連接命令一般是這么寫的:
mysql -h$ip -P$port -u$user -p
連接命令中的 mysql 是客戶端工具贞间,用來跟服務(wù)端建立連接雹仿。在完成經(jīng)典的 TCP 握手后,連接器就要開始認(rèn)證你的身份峻仇,這個時候用的就是你輸入的用戶名和密碼邑商。
- 如果用戶名或密碼不對凡蚜,你就會收到一個"Access denied for user"的錯誤吭从,然后客戶端程序結(jié)束執(zhí)行影锈。
- 如果用戶名密碼認(rèn)證通過蝉绷,連接器會到權(quán)限表里面查出你擁有的權(quán)限。之后辆床,這個連接里面的權(quán)限判斷邏輯桅狠,都將依賴于此時讀到的權(quán)限。
這就意味著咨堤,一個用戶成功建立連接后漩符,即使你用管理員賬號對這個用戶的權(quán)限做了修改嗜暴,也不會影響已經(jīng)存在連接的權(quán)限。修改完成后萎战,只有重新建立連接才會使用新的權(quán)限設(shè)置舆逃。
客戶端如果太長時間沒動靜,連接器就會自動將它斷開鸟雏。這個時間是由參數(shù) wait_timeout 控制的览祖,默認(rèn)值是 8 小時。你可以通過下面這條sql語句來查看:
mysql > show VARIABLES where variable_name='wait_timeout';
如果在連接被斷開之后又活,客戶端再次發(fā)送請求的話柳骄,就會收到一個錯誤提醒: Lost connection to MySQL server during query。這時候如果你要繼續(xù)舔清,就需要重連曲初,然后再執(zhí)行請求了。
查詢緩存
連接建立完成后抒痒,你就可以執(zhí)行 select 語句了颁褂。執(zhí)行邏輯就會來到第二步:查詢緩存。
MySQL 拿到一個查詢請求后彩届,會先到查詢緩存看看惨缆,之前是不是執(zhí)行過這條語句丰捷。之前執(zhí)行過的語句及其結(jié)果可能會以 key-value 對的形式,被直接緩存在內(nèi)存中捣染。key 是查詢的語句停巷,value 是查詢的結(jié)果畔勤。如果你的查詢能夠直接在這個緩存中找到 key,那么這個 value 就會被直接返回給客戶端庆揪。
如果語句不在查詢緩存中,就會繼續(xù)后面的執(zhí)行階段吝羞。執(zhí)行完成后钧排,執(zhí)行結(jié)果會被存入查詢緩存中。你可以看到符衔,如果查詢命中緩存糟袁,MySQL 不需要執(zhí)行后面的復(fù)雜操作,就可以直接返回結(jié)果五嫂,這個效率會很高肯尺。
但是大多數(shù)情況下则吟,建議不要使用緩存锄蹂,因為查詢緩存往往弊大于利氓仲。
MySQL為了保證查詢結(jié)果的準(zhǔn)確性,只要一個表的任何數(shù)據(jù)發(fā)生更新(包括新增得糜、修改敬扛、刪除),這個表上所有的查詢緩存都會被清空朝抖。因此對于更新數(shù)據(jù)頻繁的數(shù)據(jù)庫來說啥箭,查詢緩存的命中率會非常低。
分析器
如果沒有命中查詢緩存治宣,就要開始真正執(zhí)行語句了急侥,此時分析器開始介入。分析器的作用是侮邀,告訴MySQL你要做什么坏怪,因此它負(fù)責(zé)對 SQL 語句做解析绊茧。
分析器先會做“詞法分析”铝宵。你輸入的是由多個字符串和空格組成的一條 SQL 語句,MySQL 需要識別出里面的字符串分別是什么华畏,代表什么捉超。
MySQL 從你輸入的"select"這個關(guān)鍵字識別出來胧卤,這是一個查詢語句。它也要把字符串“T”識別成“表名 T”拼岳,把字符串“ID”識別成“列 ID”枝誊。
做完了這些識別以后,就要做“語法分析”惜纸。根據(jù)詞法分析的結(jié)果叶撒,語法分析器會根據(jù)語法規(guī)則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法耐版。
如果你的語句不對祠够,就會收到“You have an error in your SQL syntax”的錯誤提醒,比如下面這個語句 select 少打了開頭的字母“s”粪牲。
mysql> elect * from T where ID = 1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from T where ID = 1' at line 1
一般語法錯誤會提示第一個出現(xiàn)錯誤的位置古瓤,所以你要關(guān)注的是緊接“use near”的內(nèi)容。
加餐內(nèi)容:
補(bǔ)充一個我的疑問腺阳,關(guān)于上文提到的:
如果你的語句不對落君,就會收到“You have an error in your SQL syntax”的錯誤提醒,比如下面這個語句 select 少打了開頭的字母“s”亭引。
問題:為什么詞法分析沒發(fā)現(xiàn)elect不對绎速,而在語法分析里拋出這個錯誤?
網(wǎng)上一搜焙蚓,發(fā)現(xiàn)很多人和我有同樣的問題纹冤,遺憾的是都沒有答案。于是我查了一下MySQL的詞法分析的相關(guān)內(nèi)容购公,以下三段資料感覺比較有用:
詞法分析階段是編譯過程的第一個階段萌京,是編譯的基礎(chǔ)。這個階段的任務(wù)是從左到右一個字符一個字符地讀入源程序宏浩,即對構(gòu)成源程序的字符流進(jìn)行掃描然后根據(jù)構(gòu)詞規(guī)則識別單詞(也稱單詞符號或符號)知残。
詞法分析是編譯程序的第一個階段且是必要階段;詞法分析的核心任務(wù)是掃描绘闷、識別單詞且對識別出的單詞給出定性橡庞、定長的處理。詞法分析器一般以函數(shù)的形式存在印蔗,供語法分析器調(diào)用扒最。
在sql/lex.h中定義了MySQL關(guān)鍵字和函數(shù)關(guān)鍵字,用兩個數(shù)組存儲华嘹。
關(guān)鍵字 static SYMBOL symbols[]
函數(shù) static SYMBOL sql_functions[]簡單來講吧趣,就是詞法分析只負(fù)責(zé)把SQL語句中的關(guān)鍵字提取出來,但是不負(fù)責(zé)檢查語句的合法性。SQL語句合法性校驗由語法分析器完成强挫,而語法分析器會調(diào)用詞法分析器岔霸。舉個栗子,詞法分析能識別select,from,where等詞的含義俯渤,但它們之間的順序要求如何呆细,是否必須存在于SQL語句中,則由語法分析完成八匠。
優(yōu)化器
經(jīng)過了分析器絮爷,MySQL 就知道你要做什么了。在開始執(zhí)行之前梨树,還要先經(jīng)過優(yōu)化器的處理坑夯。
優(yōu)化器的作用是在表里面有多個索引的時候,決定使用哪個索引抡四;或者在一個語句有多表關(guān)聯(lián)(join)的時候柜蜈,決定各個表的連接順序。比如你有一張表T指巡,分別在c1列和c2列都建立了索引淑履,執(zhí)行下面這樣的語句:
mysql > select * from T where c1 = 0 and c2 = 0;
此時既可以使用c1列的索引,也可以使用c2列的索引厌处,這兩種執(zhí)行方法的邏輯結(jié)果是一樣的鳖谈,但是執(zhí)行的效率會有不同岁疼。優(yōu)化器的作用就是決定使用哪個索引作為最后的執(zhí)行方案阔涉。
關(guān)于優(yōu)化器是如何選擇索引的,有沒有可能選錯捷绒,本文暫不細(xì)講瑰排。
執(zhí)行器
MySQL 通過分析器知道了你要做什么,通過優(yōu)化器知道了該怎么做暖侨,于是就進(jìn)入了執(zhí)行器階段椭住,開始執(zhí)行語句。
開始執(zhí)行的時候字逗,要先判斷一下你對這個表 T 有沒有執(zhí)行查詢的權(quán)限京郑,如果沒有,就會返回沒有權(quán)限的錯誤葫掉,如下所示:
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'test'@'localhost' for table 'T'
如果有權(quán)限些举,就打開表繼續(xù)執(zhí)行。打開表的時候俭厚,執(zhí)行器就會根據(jù)表的引擎定義户魏,去使用這個引擎提供的接口。
比如我們這個例子中的表 T 中,ID 字段沒有索引叼丑,那么執(zhí)行器的執(zhí)行流程是這樣的:
- 調(diào)用存儲引擎接口取這個表的第一行关翎,判斷 ID 值是不是 10,如果不是則跳過鸠信,如果是則將這行存在結(jié)果集中纵寝;
- 調(diào)用存儲引擎接口取“下一行”,重復(fù)相同的判斷邏輯星立,直到取到這個表的最后一行店雅。
- 執(zhí)行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端。
至此贞铣,這個語句就執(zhí)行完成了闹啦。
小結(jié)
本文通過分析一條SQL語句的執(zhí)行過程,介紹了MySQL內(nèi)部各個組件的作用辕坝。
- 連接器:負(fù)責(zé)和客戶端建立連接窍奋,管理連接,并驗證用戶/密碼合法性酱畅。
- 分析器:負(fù)責(zé)對SQL語句做詞法琳袄、語法分析。
- 優(yōu)化器:負(fù)責(zé)選擇最優(yōu)索引纺酸。
- 執(zhí)行器:負(fù)責(zé)操作存儲引擎窖逗,并返回執(zhí)行結(jié)果。
希望你對一個 SQL 語句完整執(zhí)行流程的各個階段有一個初步的印象餐蔬。