當執行一條查詢語句時,MySQL內部經歷了什麼?
假如說我們有一張表 T
,表裡只有一個欄位 ID
,當我們執行下邊這條SQL語句時:
mysql> select * fron T where ID=10;
複製程式碼
在我們眼中能看到的只是輸入一條 SQL語句,返回一條查詢結果,卻不曾知道這條SQL在MySQL的內部經歷了什麼,下面我們來一步一步的分析一下;如下是MySQL的基本架構圖,從圖中可以清楚的看到SQL在MySQL中各個功能模組執行的過程:
大體來說,MySQL可以分為Server層和儲存引擎兩部分。
Server層:包括聯結器、分析器、查詢快取、優化器、執行器等。
儲存引擎:負責資料的儲存和提取。其架構模式是外掛式的,支援InnoDB、MyISAM、Memory等多種儲存模式。現如今最常用的儲存引擎是InnoDB,它從MySQL5.5.5
InnoDB
,我們也可以在 create table
時通過engine=memory
來指定儲存引擎。
從圖中可以看出:不同的儲存引擎共用同一個 server層
,也就是聯結器到執行器那一部分。
現在我們跟著開篇提到的那條SQL
進入MySQL
內部到底是怎麼執行的,Let's go
:
聯結器
首先,我們需要連線上資料庫,這時候接待我們的就是聯結器,聯結器主要負責的工作就是跟客戶端建立連線、獲取許可權、維持和管理連線。連線命令如下:
-- $ip: 伺服器IP
-- $port: MySQL埠號
-- $user: 使用者名稱
mysql -h$ip -P$port -u$user -p
複製程式碼
輸入完命令後,我們需要在互動介面輸入密碼,雖然密碼也可以在 -p 後面,解除安裝命令列中,但是這樣可能會導緻密碼洩露,不建議這麼做。
- 如果使用者名稱或密碼不對,則會收到一個
“Access denied for user”
錯誤,然後客戶端程式結束執行。 - 如果使用者名稱密碼認證通過,聯結器則會去許可權表中查詢該使用者所擁有的許可權,這個連結裡邊的許可權邏輯判斷,全都依賴於此時讀到的許可權。
這就意味著,當一個使用者成功建立連線後,即使使用管理員賬戶對其許可權做了修改,也不會立即生效,只有重新建立連結後才會使用新的許可權設定。
查詢快取
連線建立成功後,我們就可以執行SQL語句了,這時候會先來查詢快取:
MySQL
拿到一個SQL語句
之後會先到快取看看是否在此之前執行過這條語句,之前執行的語句可能會以 key-value
的形式直接快取在記憶體中。key
是查詢語句,value
是查詢結果。如果你的查詢能在快取中找到相應的key
,則直接返回其對應的value
給客戶端。
如果語句不在查詢快取中,就會繼續後面的執行階段。執行完成後,執行結果會被存入查詢快取中。你可以看到,如果查詢命中快取,MySQL
不需要執行後面的複雜操作,就可以直接返回結果,這個效率會很高。
但是同樣的快取的弊端也很大,那是因為快取失效的頻率非常頻繁,只有對一個表的更新操作,這個表上的所有查詢快取都會清空。因此很可能我們費老勁把它們給存起來,還沒來得及用,就被一個更新把快取全都給清空了,一下回到解放前。因此對於更新壓力大的資料庫來講,並不建議使用快取;如果在業務中有一張靜態表,很長時間才回去更新一次,那麼使用快取才是合適 。
MySQL
提供了一種方式設定預設不使用快取:將引數query_cache_type
設定為DEMAND
。這樣對於預設的SQL語句
則不使用快取,如果要使用快取的話可以使用SQL_CACHE
顯示的指定,如下:
mysql> select SQL_CACHE * from T where ID=10;
複製程式碼
Tips:MySQL 8.0 版本直接將查詢快取的整塊功能刪掉了,也就是說 8.0 開始徹底沒有這個功能了。
分析器
如果沒有命中快取的話,則真正開始執行語句了;首先MySQL
要知道我們要做什麼,所以要先對SQL
進行解析。
分析器首先進行詞法分析。我們輸入的SQL
是由多個字串組成的,MySQL
需要識別出來裡邊的字串分別是什麼,代表什麼意思。
MySQL
從我們輸入的select
識別出來這是一個查詢語句,它還需要把字串T
識別成表名T
,把ID
識別成列ID
。
詞法分析結束之後,需要對SQL
進行語法分析。根據詞法分析的結果,語法分析器會根據語法規則來判斷我們輸入的這條SQL
是否滿足MySQL
的語法規則。
如果我們的語法不對的話,我們會受到MySQL
的錯誤提示You have an error in your SQL syntax
,比如這條SQL
中的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
複製程式碼
一般語法錯誤會提示第一個出現錯誤的位置,所以你要關注的是緊接
user near
的內容。
優化器
經過了分析器
,MySQL
知道我們要做什麼了,在它開始執行之前,還要先經過優化器的處理。
優化器是在表裡面有多個索引的時候,決定使用哪個索引;或者在一個語句有多表關聯(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
;
這兩種執行方式的邏輯和結果都是一樣的,但是執行效率會有所不同,優化器的作用就是決定選擇哪種方案。
當優化器階段完成後,這個語句的整個執行方案就已經確定下來了,接下來就是進入執行器
階段。
執行器
MySQL
通過分析器知道了我們要做什麼,通過優化器知道了應該怎麼做,於是就進入執行器階段,開始真正的執行語句。
開始執行的時候,首先要確認我們是否有操作這個表(T)的許可權,如果沒有許可權則會返回沒有許可權的錯誤;
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
,如果不是則跳過,如果是則將這行存在結果集中。 - 呼叫引擎介面取
下一行
,重複相同的邏輯判斷,直到取到這個表的最後一行。 - 執行器將上述遍歷過程的所有滿足條件的行組成的記錄集作為結果集返回給客戶端。
至此,這個語句就執行完成了。
對於有索引的表,執行的邏輯也差不多。第一次呼叫的是“取滿足條件的第一行”這個介面,之後迴圈取“滿足條件的下一行”這個介面,這些介面都是引擎中已經定義好的。你會在資料庫的慢查詢日誌中看到一個 rows_examined 的欄位,表示這個語句執行過程中掃描了多少行。這個值就是在執行器每次呼叫引擎獲取資料行的時候累加的。
在有些場景下,執行器呼叫一次,在引擎內部則掃描了多行,因此引擎掃描行數跟 rows_examined 並不是完全相同的。