一、數據庫的基礎架構:一條SQL查詢語句是如何執行的

這一篇講解的是MySQL的基礎架構,做一件事千萬不要直接陷入到細節裏,你應該鳥瞰全貌,這樣能夠幫助你從高緯度理解問題。同樣,對於MySQL學習也是這樣,平常我們使用數據庫,看到的通常是一個整體,比如,你有個最簡單的表,表裏面只有一個ID字段,在執行下面這個查詢語句時:

select * from T where ID=10;

我們看到的只是輸入一條語句,返回一個結果,卻不知道這條語句在MySQL內部的執行過程。

現在把MySQL拆解一下,看看裏面有哪些零件,希望通過拆解,可以更加深入的理解MySQL.當碰到MySQL的一些異常或者問題時,能夠直戳本質,更加快速的解決問題。

從MySQL結構示意圖中可以清晰的看到語句在各個功能模塊中的執行過程。

MySQL可以分爲server層和存儲引擎層兩部分。

server層包括連接器、查詢緩存、分析器、優化器、執行器等,涵蓋mysql大多數核心服務功能,以及所有內置的函數(時間,日期,數學和加密函數等),所有跨引擎的功能都在這一層實現,比如存儲引擎、視圖、觸發器等。

存儲引擎負責的是數據的存儲和提取,其架構是插件式的,支持Innodb、MylSAM、Memory等多個存儲引擎,現在常用的存儲引擎是Innodb,從mysql5.5.5版本開始成爲默認的存儲引擎。在create table不指定存儲引擎時使用的是Innodb,也可以使用engine=Innodb制定使用哪一個引擎。不同的存儲引擎表數據存取方式不同,支持的功能也是不一樣的。

連接器:

當你連接數據庫時,首先接待你的就是連接器,連接器負責跟客戶端建立連接,獲取權限、維持和管理連接。連接命令:

mysql -h$ip -P$port -u$user -p

 

雖然密碼可以寫在 -p後面,但是這樣會造成密碼泄露。不建議這樣操作。

連接命令中的mysql是客戶端工具,用來跟服務器建立連接。完成經典的TCP握手後,連接器就要驗證你的身份,這時用的就是你的用戶名和密碼。

如果用戶名或密碼不對,你會收到“access denied for user”的錯誤,然後客戶端程序結束執行。

如果用戶名密碼驗證通過,連接器就會到權限表中查出你擁有的權限,之後,這個連接裏面的權限判斷邏輯,都將依賴於此時讀到的權限。

如果管理員對這個用戶的權限進行修改,也不會影響已經存在的連接權限,修改完成後,只有再新建纔會使用新的權限設置。

連接完成後,如果沒有後續操作,這個連接處於空閒操作,你可以在show processlist 命令中看到它, 穩重的圖是show provessList 的結果,其中Command列顯示的爲‘sleep’的這一行,就表示現在系統裏面有一個空閒連接

客戶端如果太長時間沒有動靜,連接器就會自動斷開它,這個參數有wait_timeout控制的,默認爲8小時。

如果斷開連接後,客戶端再次請求,就會有:lost connection to mysql server during query.這時候如果你要繼續,就需要重連,然後再執行請求。

長連接:

連接成功後,如果客戶端持續有請求,則一直使用同一個連接。

短連接:每次執行完很少的幾次查詢之後就斷開連接,下次需要查詢時再重新連接。

但是全部使用長連接,會發現有時候mysql佔用的內存漲的特別快,這是因爲mysql在執行過程中臨時使用內存是管理在連接對象裏面的,這些資源會在連接斷開的時候才釋放,所以長連接累計下來,可能會導致內存佔用過大,被系統強行殺掉(OOM),從現象來看就是mysql異常重啓了。

可以考慮以下兩種方案:

1、定期斷開長連接,使用一段時間,或者程序判斷執行過一個佔用內存的大查詢後,斷開重連,之後要查詢在重連。

2、如果你使用mysql5.7或更新的版本,可以每次執行一個比較大的操作後,通過執行mysql_reset_connection來重新初始化連接資源,這個過程不需要重連和進行權限校驗,但是會將連接恢復到剛剛穿件完時的狀態。

查詢緩存:

連接建立之後,你就可以執行select語句了,執行語句就會來到第二步:查詢緩存。

mysql拿到一個查詢請求後,會先到緩存看看,之前是不是執行過這條語句,之前執行過的語句及其結果會以key-value的

形式存在,被直接緩存在內存中。ke是查詢語句,value是查詢結果,如果你的查詢能在緩存中找到key,那麼這個value就會直接返回結果,這個效率會很高。

大多數情況下不要使用查詢緩存,因爲利大於弊。

查詢緩存失效特別頻繁,一個更新語句就會將表上所有的查詢緩存清空。因此,你費勁地把結果存起來,還沒有用就被一個更新清空了。對於更新壓力大的表,查詢緩存的命中率非常低,除非你的業務就是一張靜態表,很長時間纔會更新一次。比如系統配置表。

好在mysql提供了這種按需使用的方式,你可以將參數query_cache_type設置成DEMAND.這樣對於默認的sql語句都不使用查詢緩存,對於你確定使用的查詢緩存可以使用SQL_CACHE來顯示指定。

mysql> select SQL_CACHE * from T where ID=10;

mysql 8.0版本直接將查詢緩存的整塊功能刪除掉了,也就是在8.0開始就沒有查詢緩存的功能了。

分析器:

mysql需要知道你要做什麼,因此需要對sql語句做解析。

1、分析器先做詞法分析,你輸入的時由多個字符串和空格組成的一條sql語句,mysql需要識別出裏面的字符串分別代表的是什麼。

mysql從你輸入的select這個關鍵字識別出來,這是一個查詢語句,他需要把T識別爲表名,把id識別成列名。(mysql不區分字母的大小寫)。

2、之後分析器做語法分析,根據語法分析的結果,語法分析器會根據語法規則,判斷你輸入的sqi語句是否maysql語法。

如果你的語法有錯誤,會有"you have an error in your syntax"的錯誤提示。

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

 

一般語法錯誤會提示第一個出現錯誤的位置,所以你要關注的是“use near” 的內容。

優化器:

經過分析器,mysql知道了你要做什麼,在執行之前,還要先經過優化器的處理。

優化器在表裏面有多個索引的時候,決定使用哪個索引,或者一個語句有多表關聯(join)的時候,決定各個表的順序,下面表執行兩個表的join:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;

 

1、可以從t1表裏面取出c=10的記錄的ID 值,再根據ID值關聯到t2,再判斷t2裏面d的值是否等於20.。

2、也可以先從t2裏面取出d=20的記錄的ID 值,再根據ID 值關聯到t1,再判斷t1 裏面c 的值是否等於 10。

兩種執行方法的邏輯是一樣的,但是執行的效率是不同的,而優化器決定的是使用哪一種方案。

優化器階段完成後,這個語句的執行方案就確定下來了,然後進入了執行階段。

執行器:

mysql通過執行器開始執行語句了。

開始執行的時候,要先判斷你對這個表是否有執行查詢的權限,如果沒有就會返回沒有權限的錯誤,如下所示(在工程實現上,如果命中查詢緩存,會在查詢緩存返回結果的時候,做權限校驗,查詢也會在優化器之前調用precheck驗證權限)。

mysql> select * from T where ID=10;

ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有權限,就打開表繼續執行,打開表的時候。執行器就會根據表的引擎定義,去使用這個引擎提供的接口。

比如例子中表T,ID字段沒有索引,那麼執行器的執行流程如下:

1、調用InnoDB引擎接口取這個表額第一行,判斷ID是不是10,如果不是就跳過,如果是將這行存在結果集中;

2、調用引擎取下一行,重複相同的判斷邏輯,直到這個表的最後一行。

3、執行器將上述便利結果中所用滿足的條件組成記錄集作爲結果集返回給客戶端。

至此,這個語句就執行結束。

對於有索引的表,第一次調用的是取滿足條件的第一行這個接口,之後再循環滿足條件的下一行這個接口,這些接口在引擎中都定義好了。

你會在慢查詢日誌中看到一個 rows_examined的字段,表示語句執行過程中掃描了多少行,這個值就是執行器每次調用引擎獲取數據累加的。

在有些場景下,執行器調用一次,在引擎內部則掃描了多行,因此引擎掃描行數跟rows_examined並不是完全相同的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章