原來select語句在MySQL中是這樣執行的!看完又漲見識了!這回我要碾壓面試官!

冰河團隊發表於2021-08-03

大家好,我是冰河~~

MySQL作為網際網路行業使用最多的關係型資料庫之一,與其免費、開源的特性是密不可分的。然而,很多小夥伴工作了很多年,只知道使用MySQL進行CRUD操作,這也導致很多小夥伴工作多年後,想跳槽進入大廠,卻在面試的時候屢屢碰壁。

問個簡單的問題:select語句是如何在MySQL中執行的? 這也是很多面試官喜歡問的問題,如果你連這個簡單的問題都不能回答的話,那就要好好規劃下自己的職業生涯了。

好了,今天我們就一起來聊聊select語句是如何在MySQL中執行的。文章的主要內容如下。

頻繁使用的select語句

為了更好地貫穿全文,這裡先來列舉一個最簡單的select查詢語句,例如:查詢user表中id為1001的使用者資訊,使用下面的SQL語句進行查詢。

select * from user where user_id = 1001;

當我們在MySQL的命令列中輸入上述SQL語句時,這條SQL語句到底在MySQL中是如何執行的呢?接下來,我們就以這條SQL語句為例,說說select語句是如何在MySQL中執行的。

MySQL邏輯架構

在介紹select語句在MySQL中的執行流程之前,我們先來看看MySQL的邏輯架構,因為任何SQL語句的執行都離不開MySQL邏輯架構的支撐。也就是說,SQL語句在MySQL中的執行流程與MySQL的邏輯架構是密不可分的。

在上圖中,我們簡單的畫了下MySQL的邏輯架構圖,並且給出了邏輯分層和每層中各部分的功能。從邏輯上,我們可以將MySQL粗略地分成三層:Server層、儲存引擎層和系統檔案層,而Server層中又可以分成網路連線層(聯結器)和資料服務層(Server層)。

Server層中包含了聯結器、查詢快取、分析器、優化器和執行器等MySQL的核心組成部分,另外,在Server層中還包含了所有的內建函式(比如:日期時間函式、加解密函式、聚合函式、數學函式等),儲存引擎、觸發器、檢視等等。

儲存引擎層主要負責和系統檔案層進行互動,儲存引擎層本身是外掛式的架構設計,支援InnoDB、MyISAM、Archive、Memory等儲存引擎。在MySQL 5.5.5及以後的版本中,MySQL的預設儲存引擎是InnoDB。

系統檔案層主要負責儲存實際的資料,將資料以檔案的形式儲存到伺服器的磁碟上。

接下來,我們就來說說一條select語句在MySQL的邏輯架構的每一部分到底是如何執行的。

聯結器是如何授權的?

首先,我們先來看看在伺服器命令列輸入連線MySQL的命令時,MySQL的聯結器是如何進行驗證的。比如,我們在伺服器的命令列輸入瞭如下命令。

mysql -ubinghe -p

執行“回車”後,輸入binghe賬戶的密碼,與MySQL進行連線。此時,連線的過程需要完成經典的TCP握手操作(有關TCP的握手相關的知識,小夥伴們可以參考《【面經】面試官:講講七層網路模型與TCP三次握手與四次斷開?》)。之後,聯結器就開始認證連線的身份是否合法,最直接的就是驗證使用者名稱和密碼是否正確。

如果使用者名稱或者密碼錯誤,MySQL會提示 Access denied for user 。如果使用者名稱和密碼正確,則聯結器會到MySQL的許可權表中查詢當前連線擁有的許可權。查詢到許可權之後,只要這個連線沒有斷開,則這個連線涉及到的許可權操作都會依賴此時查詢到的許可權。

換句話說,一個使用者登入MySQL併成功連線MySQL後,哪怕是管理員對當前使用者的許可權進行了修改操作,此時只要這個使用者沒有斷開MySQL的連線,就不會受到管理修改許可權的影響。管理員修改許可權後,只有對新建的連線起作用。

如果客戶端連線MySQL後,長時間沒有執行任何操作,則聯結器會自動斷開與這個客戶端的連線。具體多長時間斷開是由MySQL的引數wait_timeout控制的,這個值預設是8小時。我們可以根據實際業務需要,自行調整這個引數的值,以使MySQL能夠滿足我們的實際業務場景。

由於客戶端與MySQL的連線是比較複雜的,這個過程也是比較耗時的,它會涉及TCP的握手操作,還會查詢當前連線的許可權資訊等。往往在實際的工作過程中,我們會使用資料庫連線池的方式,將資料庫的連線快取起來,這就意味著我們是使用長連線與MySQL進行互動的。

但是使用長連線連線MySQL也會有一個問題:那就是有時候會發現MySQL佔用的記憶體漲得特別快,這是因為MySQL在執行的過程中,使用的臨時記憶體是在連線物件裡面進行管理的。這些佔用的資源只有在連線斷開的時候,才會被釋放。如果連線長時間不釋放,就會出現大量的臨時記憶體佔用記憶體空間。如果時間久了,可能會導致佔用過多的記憶體,從而被作業系統“消滅”了,給人的感覺就是MySQL意外重啟了。

我們可以使用如下的方案來解決這個問題:

  • 定期或者執行過一個比較佔記憶體的查詢操作後,斷開連線,以後再重新建立和MySQL的連線。
  • 如果使用MySQL 5.7或更新的MySQL版本,可以通過執行mysql_reset_connection重新初始化MySQL的資源。重新初始化的過程不會重新連線MySQL,也不會重新做許可權的驗證操作。

查詢快取的作用是什麼?

登入MySQL後,客戶端就會與MySQL建立連線,此時執行select語句時,首先會到查詢快取中查詢是否執行過當前select語句。如果之前執行過相應的select語句,則執行過的select語句和查詢結果會以key-value的形式存放在查詢快取中,其中,key是查詢語句,value是查詢的結果資料。

如果在查詢快取中沒有找到相應的資料,則會繼續執行後續的查詢階段。執行完成後,會將結果快取到查詢快取中。後續的查詢如果命中快取,則直接返回查詢快取中的資料,效能還是挺高的。

但是,大多數時候我不太建議小夥伴們開啟查詢快取,為啥?原因很簡單:查詢快取失效的頻率是非常頻繁的,只要對一個表進行更新操作,則這張表上所有的查詢快取都會被清空。 而且在MySQL 8.0中,直接刪除了查詢快取的功能(冰河在看MySQL原始碼時,也證明了這一點)。

分析器對select語句做了什麼?

分析器主要是對select語句進行 詞法分析和語法分析 操作。

如果select語句沒有命中快取,則首先會由分析器對其進行“詞法分析”操作,此時,MySQL會識別select語句中的每個字串代表什麼含義。

例如,MySQL會通過"select"關鍵字識別出這是一個查詢語句,也會把"user"識別為"資料表名user",把"id"識別成"欄位名id"。接下來,就要進行“語法分析了”,根據語法規則,判斷select語句是否滿足MySQL的語法。如果判斷出輸入的SQL語句不滿足語法規則,則MySQL會提示相應的錯誤資訊。

優化器是如何優化select語句的?

對select語句進行了詞法分析和語法分析後,還要經過優化器的優化處理才能執行。比如,我們的select語句中如果使用了多個索引,則優化器會決定使用哪個索引來查詢資料;再比如,在select語句中,有多表關聯的操作,優化器會決定各表的連線順序,資料表的連線順序不同,對於執行的效率會大不相同,優化器往往會選擇使用查詢效率高的連線順序。

如果select語句經過優化器的優化之後,就會進入執行階段了。

執行器如何執行select語句?

進入執行階段的select語句,首先,執行器會對當前連線進行許可權檢查,最直接的方式就是檢查當前連線是否對資料表user具有查詢許可權。如果當前連線對資料表user沒有查詢許可權,就會返回沒有許可權的錯誤。例如,會返回如下錯誤。

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

如果當前連線具有對資料表user的查詢許可權,則會繼續執行。首先會進行開啟資料表的操作,此時優化器會根據建立表時使用的儲存引擎,使用相應儲存引擎的介面執行查詢操作。這裡,我們舉一個例子:

假設,我們在id欄位上沒有建立索引,執行器執行的流程大致如下所示。

(1)通過儲存引擎讀取資料表user的第一行資料,判斷當前行的id值是否等於1001,如果不等於1001,則繼續讀取下一行資料;如果等於1001,則將當前行放入結果集中。

(2)繼續通過儲存引擎讀取下一行資料,執行與(1)相同的邏輯判斷,直到處理完user表中的所有資料。

(3)處理完所有的資料後,執行器就會將結果集中的資料返回給客戶端。

如果在id欄位上有索引的話,執行的整體邏輯與id欄位上沒有索引大體一致。

如果開啟了慢查詢的話,執行select語句時,會在慢查詢日誌中輸出一個rows_examined欄位,這個欄位表示select語句在執行的過程中掃描了資料表中的多少行資料。不過在有些場景下,執行器呼叫一次,儲存引擎內部會會掃描多行,這就導致儲存引擎掃描的行數與rows_examined欄位標識的行數並不完全相同。

好了,今天就到這兒吧,我是冰河,我們下期見~~

相關文章