深入學習MySQL,從概覽MySQL邏輯架構開始。
首先來看一下MySQL的邏輯架構圖:
MySQL邏輯架構大概可以分為三層:
- 客戶端:最上層的服務並不是MySQL所獨有的,大多數基於網路的客戶端/伺服器的工具或者服務都有類似的架構。比如連線處理、授權認證、安全等等。
- Server層:大多數MySQL的核心服務功能都在這一層,包括查詢解析、分析、優化、快取以及所有的內建函式(例如,日期、時間、數學和加密函式),所有跨儲存引擎的功能都在這一層實現:儲存過程、觸發器、檢視等。
- 儲存引擎層:第三層包含了儲存引擎。儲存引擎負責MySQL中資料的儲存和提取。Server層通過API與儲存引擎進行通訊。這些介面遮蔽了不同儲存引擎之間的差異,使得這些差異對上層的查詢過程透明。
值得一提的是在MySQL8.0中取消了查詢快取,大概的理由是查詢快取存在嚴重的可伸縮性問題,並且很容易成為嚴重的瓶頸快取,將快取移動到客戶端能收穫更好的效能。
通過一條查詢語句的執行過程,來了解一些關鍵的部件:
mysql> select * from T where ID=10;
1、聯結器
首先,需要連線資料庫。
當客戶端(應用)連線到MySQL伺服器時,伺服器需要對其進行認證。認證基於使用者名稱、原始主機資訊和密碼。
連線命令:
mysql -h$ip -P$port -u$user -p
除了基本認證之外,聯結器還會進行一些執行緒的處理。
每個客戶端連線都會在伺服器程式中擁有一個執行緒,這個連線的查詢只會在這個單獨的執行緒中執行,該執行緒只能輪流在某個CPU核心或者CPU中執行。伺服器會負責快取執行緒,因此不需要為每一個新建的連線建立或者銷燬執行緒。
2、查詢快取
對於SELECT語句,在解析查詢之前,伺服器會先檢查查詢快取(Query Cache),如果能夠在其中找到對應的查詢,伺服器就不必再執行查詢解析、優化和執行的整個過程,而是直接返回查詢快取中的結果集。
但不推薦使用查詢快取,為什麼呢?因為查詢快取往往弊大於利。
查詢快取的失效非常頻繁,只要有對一個表的更新,這個表上所有的查詢快取都會被清空。對於更新壓力大的資料庫來說,查詢快取的命中率會非常低。除非你的業務就是有一張靜態表,很長時間才會更新一次。比如,一個系統配置表,那這張表上的查詢才適合使用查詢快取。
好在MySQL也提供了這種“按需使用”的方式。可以將引數query_cache_type設定成DEMAND,這樣對於預設的SQL語句都不使用查詢快取。而對於確定要使用查詢快取的語句,可以用SQL_CACHE顯式指定,如下:
mysql> select SQL_CACHE * from T where ID=10;
上面也提到了MySQL8.0徹底廢棄了查詢快取的功能。
3、解析器
如果快取沒有命中的話,MySQL會對查詢語句進行解析。簡單說解析的作用將我們人能看懂的SQL解析成MySQ能識別的語言。
解析器先會做“詞法解析”。輸入的是由多個字串和空格組成的一條SQL語句,MySQL需要識別出裡面的字串分別是什麼,代表什麼。
MySQL從輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字串“T”識別成“表名T”,把字串“ID”識別成“列ID”。
做完了這些識別以後,就要做“語法解析”。根據詞法解析的結果,語法解析器會根據語法規則,判斷輸入的這個SQL語句是否滿足MySQL語法。
4、優化器
經過了解析器器,MySQL知道我們要幹什麼。
接下來並不是直接執行,而是會在優化器這一層進行優化,優化器是個非常複雜的部件,它會幫我去使用他自己認為的最好的方式去優化這條 SQL 語句,並生成一條條的執行計劃。
例如在表裡面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(join)的時候,決定各個表的連線順序。比如你執行下面這樣的語句,這個語句是執行兩個表的join:
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
- 既可以先從表t1裡面取出c=10的記錄的ID值,再根據ID值關聯到表t2,再判斷t2裡面d的值是否等於20。
- 也可以先從表t2裡面取出d=20的記錄的ID值,再根據ID值關聯到t1,再判斷t1裡面c的值是否等於10。
這兩種執行方法的邏輯結果是一樣的,但是執行的效率會有不同,而優化器的作用就是決定選擇使用哪一個方案。
優化器階段完成後,這個語句的執行方案就確定下來了,然後進入執行器階段。如果你還有一些疑問,比如優化器是怎麼選擇索引的,有沒有可能選擇錯等等,沒關係,我會在後面的文章中單獨展開說明優化器的內容。
5、執行器
MySQL通過解析器知道了你要做什麼,通過優化器知道了該怎麼做,於是就進入了執行器階段,執行器會根據一系列的執行計劃去呼叫儲存引擎的介面去完成SQL的執行。
開始執行的時候,要先判斷一下你對這個表T有沒有執行查詢的許可權,如果沒有,就會返回沒有許可權的錯誤,如下所示(在工程實現上,如果命中查詢快取,會在查詢快取放回結果的時候,做許可權驗證。查詢也會在優化器之前呼叫precheck驗證許可權)。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有許可權,就開啟表繼續執行。開啟表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的介面。
比如我們這個例子中的表T中,ID欄位沒有索引,那麼執行器的執行流程是這樣的:
- 呼叫InnoDB引擎介面取這個表的第一行,判斷ID值是不是10,如果不是則跳過,如果是則將這行存在結果集中;
- 呼叫引擎介面取“下一行”,重複相同的判斷邏輯,直到取到這個表的最後一行。
- 執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端。
至此,這個語句就執行完成了。
對於有索引的表,執行的邏輯也差不多。第一次呼叫的是“取滿足條件的第一行”這個介面,之後迴圈取“滿足條件的下一行”這個介面,這些介面都是引擎中已經定義好的。
參考:
【1】:《高效能MySQL》
【2】:極客時間 《MySQL實戰45講》
【3】:《MySQL技術內幕 InnoDB儲存引擎》