select * from user 這條 SQL 語句,背後藏着哪些不可告人的祕密?

作爲一名 Java開發人員,寫 SQL 語句是常有的事,但是你知道 SQL 語句背後的處理邏輯嗎?比如下面這條 SQL 語句:

select * from user where id=1

執行完這條語句後,我們就會得到 id 爲 1 的用戶信息。那麼對於這一條 SQL 語句,MySQL服務器做了哪些處理呢?這篇文章我們就一起打卡 MySQL 數據庫中對 SQL 語句的處理邏輯。

瞭解 MySQL 數據庫的 SQL 語句內部處理邏輯有什麼好處?當我們碰到 MySQL 的一些異常或者問題時,就能夠直戳本質,更爲快速地定位並解決問題

想要更好的瞭解 SQL 語句的內部處理邏輯,我們可以先看 MySQL 的基本架構圖,這樣我們可以站在更高的角度去俯瞰 MySQL 數據庫,MySQL 的基本架構示意圖如下:

MySQL 的基本架構示意圖

從圖中,我們可以清晰的看出 MySQL 的架構和各個模塊以及 SQL 語句的執行過程,MySQL 數據庫整體可以分爲 Server 層和存儲引擎層兩部分,其中 Server 層是共有的,而存儲引擎層則是可以以插件的形式進行擴展。一條 SQL 語句大概會經歷鏈接管理、解析與優化、最後到存儲引擎,這三個模塊。接下來我們就來聊一聊這三個模塊。

連接管理

連接管理是 SQL 語句執行過程中碰到的第一關,鏈接管理就像一扇大門一樣,控制着客戶端與 Server 服務端的交互,連接管理主要工作是客戶端的身份認證和連接線程的管理。

每個客戶端與 Server 建立連接時,服務端都會創建一個線程來與客戶端進行交互,交互的第一項內容就是驗證客戶端的身份,認證憑據是基於客戶端發起連接請求時攜帶的主機信息、用戶名、密碼。如果認證失敗,則結束連接任務,並且返回的 Access denied for user 錯誤。

如果認證成功,連接管理還會做一件事情,到權限表中查詢出該用戶的權限,在這次連接下,後續的權限判斷都是基於此時讀取的權限爲依據,也就是說連接成功後,即使管理員對這個用戶做了權限修改,也不會影響這次連接的權限驗證。

連接管理需要做的事情就比較簡單,主要是負責客戶端與服務端進行連接,當然在連接線程上,連接管理也做了優化,並不是每個客戶端執行完任務之後,就把該線程銷燬,連接管理會把這些線程緩存起來,等待新的連接,這也就不會頻繁的創建和銷燬線程,從而節約了開銷。

解析與優化

完成連接管理之後,SQL 語句執行的第二步就是解析和優化,這一步就非常的複雜,SQL 語句查詢的所有操作都在這裏了。我們可以將這一步細分爲 4 小步。

查詢緩存

在 MySQL 服務端也有緩存,這是一個非常雞肋的功能,爲什麼呢?看完了你就知道了。

MySQL 服務器拿到查詢請求後,會先到查詢緩存看看,之前是不是執行過這條語句。之前執行過的語句及其結果可能會以 key-value 對的形式,被直接緩存在內存中。key 是查詢的語句,value 是查詢的結果。如果你的查詢能夠直接在這個緩存中找到 key,那麼這個 value 就會被直接返回給客戶端。如果語句不在查詢緩存中,就會繼續後面的執行階段。執行完成後,執行結果會被存入查詢緩存中。

看上去沒毛病,這樣做會大大提升 MySQL 的性能,然而,你想多了,MySQL 的查詢緩存命中率非常的低,主要原因是如果兩個查詢請求在任何字符上的不同(例如:空格、註釋、大小寫),都會導致緩存不會命中。

還有就是緩存有可能獲取到錯誤的數據,以某些系統函數舉例,可能同樣的函數的兩次調用會產生不一樣的結果,比如函數NOW,每次調用都會產生最新的當前時間,如果在一個查詢請求中調用了這個函數,那即使查詢請求的文本信息都一樣,那不同時間的兩次查詢也應該得到不同的結果,如果在第一次查詢時就緩存了,那第二次查詢的時候直接使用第一次查詢的結果就是錯誤的!

除了這些之外,MySQL 緩存的失效也非常的頻繁,MySQL的緩存系統會監測涉及到的每張表,只要該表的結構或者數據被修改,如對該表使用了 INSERT、 UPDATE、DELETE、TRUNCATE TABLE、ALTER TABLE、DROP TABLE 或 DROP DATABASE 語句,那使用該表的所有高速緩存查詢都將變爲無效並從高速緩存中刪除!

看到這裏你知道查詢緩存很雞肋了吧,緩存對 MySQL 數據庫來說弊大於利,所以在 MySQL 8.0 版本直接將查詢緩存的整塊功能刪掉了

語法解析和預處理

如果查詢緩存沒有命中,接下來就需要進入正式的查詢階段了。因爲客戶端程序發送過來的請求只是一段文本而已,所以 MySQL 服務器程序首先要對這段文本做語法解析。

首先通過關鍵字將 SQL 語句進行解析,並且生成一個“解析樹”。MySQL 解析器將使用 MySQL 語法規則驗證和解析查詢,例如,關鍵字是否使用正確、關鍵字的順序是否正確或者引號是否前後匹配等。

預處理是根據一些 MySQL 規則進一步檢查解析樹是否合法,例如數據表和數據列是否存在,還會解析名字和別名,看看他們是否有歧義等。

查詢優化

語法解析和預處理之後,你的需求就明白了,需要查詢哪張表,查詢的數據列是哪些、條件是什麼等等。但是使用怎麼樣的方式是最優查詢方式呢?查詢優化就是來幹這個事的,MySQL 的優化程序會對我們的語句做一些優化,如外連接轉換爲內連接、表達式簡化、子查詢轉爲連接等等。優化的結果就是生成一個執行計劃,這個執行計劃表明了應該使用哪些索引進行查詢,表之間的連接順序是啥樣的。

執行器

執行器會執行查詢優化後的執行計劃,通過與存儲引擎交互,完成數據的查詢操作,返回最終的數據結果。

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


mysql> select * from user where ID=1;

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

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

比如我們這個例子中的表 user 中,假設 ID 字段沒有索引,那麼執行器的執行流程是這樣的:

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

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

3、執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作爲結果集返回給客戶端。

到這裏,執行 SQL 語句就執行完了,其實這內部還是非常複雜的。

存儲引擎

到上面爲止,SQL 語句就執行完了,但是與真實數據打交道的是存儲引擎,存儲引擎是 MySQL服務器對數據的存儲和提取操作的封裝模塊。我們知道表是由一行一行的記錄組成的,但這只是一個邏輯上的概念,物理上如何表示記錄,怎麼從表中讀取數據,怎麼把數據寫入具體的物理存儲器上,這都是存儲引擎負責的事情。

爲了實現不同的功能,MySQL提供了各式各樣的存儲引擎,不同存儲引擎管理的表具體的存儲結構可能不同,採用的存取算法也可能不同。比如,MySQL5.7 之後默認的 InnoDB 存儲引擎。

可以看出一條 SQL 語句的執行還是非常複雜的,涉及到了很多的模塊,文章到這裏就結束了,感謝您的閱讀,希望這篇文章對你的學習和工作有所幫助,如果您覺得文章有用,歡迎點贊+轉發。

最後

目前互聯網上很多大佬都有 MySQL 內部架構相關文章,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支持。若文中有所錯誤之處,還望提出,謝謝。

歡迎掃碼關注微信公衆號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。

平頭哥的技術博文

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