從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

本文來自公眾號狸貓技術窩的專欄《從零開始帶你成為MySQL實戰優化高手》


1、把MySQL當個黑盒子一樣執行SQL語句

我們的系統採用數據庫連接池的方式去併發訪問數據庫,然後數據庫自己其實也會維護一個連接池,其中管理了各種系統跟這臺數據庫服務器建立的所有連接

我們先看下圖回顧一下

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

當我們的系統只要能從數據庫連接池獲取到一個數據庫連接之後,我們就可以執行增刪改查的SQL語句了

從上圖其實我們就可以看到,我們可以通過數據庫連接把要執行的SQL語句發送給MySQL數據庫。

然後呢?大部分同學瞭解到這個程度就停下來了,然後大家覺得要關注的可能主要就是數據庫裡的表結構,建了哪些索引,然後就按照SQL語法去編寫增刪改查SQL語句,把MySQL當個黑盒子去執行SQL語句就可以了。

我們只知道執行了insert語句之後,在表裡會多出來一條數據;執行了update語句之後,會對錶裡的數據進行更改;執行了delete語句之後,會把表裡的一條數據刪除掉;執行了select語句之後,會從表裡查詢一些數據出來。

如果語句性能有點差?沒關係,在表裡建幾個索引就可以了!可能這就是目前行業內很多工程師對數據庫的一個認知,完全當他是個黑盒子,來建表以及執行SQL語句。


2、一個不變的原則:網絡連接必須讓線程來處理

現在假設我們的數據庫服務器的連接池中的某個連接接收到了網絡請求,假設就是一條SQL語句,那麼大家先思考一個問題,誰負責從這個連接中去監聽網絡請求?誰負責從網絡連接裡把請求數據讀取出來?

我想很多人恐怕都沒思考過這個問題,但是如果大家對計算機基礎知識有一個簡單瞭解的話,應該或多或少知道一點,那就是網絡連接必須得分配給一個線程去進行處理,由一個線程來監聽請求以及讀取請求數據,比如從網絡連接中讀取和解析出來一條我們的系統發送過去的SQL語句,如下圖所示:

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

3、SQL接口:負責處理接收到的SQL語句

接著我們來思考一下,當MySQL內部的工作線程從一個網絡連接中讀取出來一個SQL語句之後,此時會如何來執行這個SQL語句呢?

其實SQL是一項偉大的發明,他發明了簡單易用的數據讀寫的語法和模型,哪怕是個產品經理,或者是運營專員,甚至是銷售專員,即使他不會技術,他也能輕鬆學會使用SQL語句。

但如果你要去執行這個SQL語句,去完成底層數據的增刪改查,那這就是一項極度複雜的任務了!

所以MySQL內部首先提供了一個組件,就是SQL接口(SQL Interface),他是一套執行SQL語句的接口,專門用於執行我們發送給MySQL的那些增刪改查的SQL語句

因此MySQL的工作線程接收到SQL語句之後,就會轉交給SQL接口去執行,如下圖。

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

4、查詢解析器:讓MySQL能看懂SQL語句

接著下一個問題來了,SQL接口怎麼執行SQL語句呢?你直接把SQL語句交給MySQL,他能看懂和理解這些SQL語句嗎?

比如我們來舉一個例子,現在我們有這麼一個SQL語句:

select id,name,age from users where id=1


這個SQL語句,我們用人腦是直接就可以處理一下,只要懂SQL語法的人,立馬大家就知道他是什麼意思,但是MySQL自己本身也是一個系統,是一個數據庫管理系統,他沒法直接理解這些SQL語句!

所以此時有一個關鍵的組件要出場了,那就是查詢解析器

這個查詢解析器(Parser)就是負責對SQL語句進行解析的,比如對上面那個SQL語句進行一下拆解,拆解成以下幾個部分:

  1. 我們現在要從“users”表裡查詢數據
  2. 查詢“id”字段的值等於1的那行數據
  3. 對查出來的那行數據要提取裡面的“id,name,age”三個字段。

所謂的SQL解析,就是按照既定的SQL語法,對我們按照SQL語法規則編寫的SQL語句進行解析,然後理解這個SQL語句要幹什麼事情,如下圖所示:

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

5、查詢優化器:選擇最優的查詢路徑

當我們通過解析器理解了SQL語句要幹什麼之後,接著會找查詢優化器(Optimizer)來選擇一個最優的查詢路徑。

可能有同學這裡就不太理解什麼是最優的查詢路徑了,這個看起來確實很抽象,當然,這個查詢優化器的工作原理,後續將會是我們分析的重點,大家現在不用去糾結他的原理。

但是我們可以用一個極為通俗簡單的例子,讓大家理解一下所謂的最優查詢路徑是什麼。

就用我們剛才講的那個例子好了,我們現在理解了一個SQL想要幹這麼一個事兒:我們現在要從“users”表裡查詢數據,查詢“id”字段的值等於1的那行數據,對查出來的那行數據要提取裡面的“id,name,age”三個字段。

事是明白了,但是到底應該怎麼來實現呢?

你看,要完成這個事兒我們有以下幾個查詢路徑(純屬用於大家理解的例子,不代表真實的MySQL原理,但是通過這個例子,大家肯定能理解所謂最優查詢路徑的意思):


  1. 直接定位到“users”表中的“id”字段等於1的一行數據,然後查出來那行數據的“id,name,age”三個字段的值就可以了
  2. 先把“users”表中的每一行數據的“id,name,age”三個字段的值都查出來,然後從這批數據裡過濾出來“id”字段等於1的那行數據的“id,name,age”三個字段


上面這就是一個最簡單的SQL語句的兩種實現路徑,其實我們會發現,要完成這個SQL語句的目標,兩個路徑都可以做到,但是哪一種更好呢?顯然感覺上是第一種查詢路徑更好一些。

所以查詢優化器大概就是幹這個的,他會針對你編寫的幾十行、幾百行甚至上千行的複雜SQL語句生成查詢路徑樹,然後從裡面選擇一條最優的查詢路徑出來。

相當於他會告訴你,你應該按照一個什麼樣的步驟和順序,去執行哪些操作,然後一步一步的把SQL語句就給完成了。

我們來一起看看下面的圖:

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

6、調用存儲引擎接口,真正執行SQL語句

最後一步,就是把查詢優化器選擇的最優查詢路徑,也就是你到底應該按照一個什麼樣的順序和步驟去執行這個SQL語句的計劃,把這個計劃交給底層的存儲引擎去真正的執行。

這個存儲引擎是MySQL的架構設計中很有特色的一個環節。

不知道大家是否思考過,真正在執行SQL語句的時候,要不然是更新數據,要不然是查詢數據,那麼數據你覺得存放在哪裡?

說白了,數據庫也不是什麼神秘莫測的東西,你可以把他理解為本身就是一個類似你平時寫的圖書館管理系統、電信計費系統、電商訂單系統之類的系統罷了。

數據庫自己就是一個編程語言寫出來的系統而已,然後啟動之後也是一個進程,執行他裡面的各種代碼,也就是我們上面所說的那些東西。所以對數據庫而言,我們的數據要不然是放在內存裡,要不然是放在磁盤文件裡,沒什麼特殊的地方!

所以我們來思考一下,假設我們的數據有的存放在內存裡,有的存放在磁盤文件裡,如下圖所示。

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

那麼現在問題來了,我們已經知道一個SQL語句要如何執行了,但是我們現在怎麼知道哪些數據在內存裡?哪些數據在磁盤裡?我們執行的時候是更新內存的數據?還是更新磁盤的數據?我們如果更新磁盤的數據,是先查詢哪個磁盤文件,再更新哪個磁盤文件?

是不是感覺一頭霧水

所以這個時候就需要存儲引擎了,存儲引擎其實就是執行SQL語句的,他會按照一定的步驟去查詢內存緩存數據,更新磁盤數據,查詢磁盤數據,等等,執行諸如此類的一系列的操作,如下圖所示。

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?

MySQL的架構設計中,SQL接口、SQL解析器、查詢優化器其實都是通用的,他就是一套組件而已。

但是存儲引擎的話,他是支持各種各樣的存儲引擎的,比如我們常見的InnoDB、MyISAM、Memory等等,我們是可以選擇使用哪種存儲引擎來負責具體的SQL語句執行的。

當然現在MySQL一般都是使用InnoDB存儲引擎的,至於存儲引擎的原理,後續我們也會深入一步一步分析,大家不必著急。

7、執行器:根據執行計劃調用存儲引擎的接口

那麼看完存儲引擎之後,我們回過頭來思考一個問題,存儲引擎可以幫助我們去訪問內存以及磁盤上的數據,那麼是誰來調用存儲引擎的接口呢?

其實我們現在還漏了一個執行器的概念,這個執行器會根據優化器選擇的執行方案,去調用存儲引擎的接口按照一定的順序和步驟,就把SQL語句的邏輯給執行了。

舉個例子,比如執行器可能會先調用存儲引擎的一個接口,去獲取“users”表中的第一行數據,然後判斷一下這個數據的“id”字段的值是否等於我們期望的一個值,如果不是的話,那就繼續調用存儲引擎的接口,去獲取“users”表的下一行數據。

就是基於上述的思路,

執行器就會去根據我們的優化器生成的一套執行計劃,然後不停的調用存儲引擎的各種接口去完成SQL語句的執行計劃,大致就是不停的更新或者提取一些數據出來

我們看下圖的示意

從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的?


分享到:


相關文章: