1. 程式人生 > 資料庫 >從 MySQL 架構設計出發,看它是如何執行一條 SQL 語句的

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

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

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

我們先看下圖回顧一下
在這裡插入圖片描述
當我們的系統只要能從資料庫連線池獲取到一個數據庫連線之後,我們就可以執行增刪改查的SQL語句了

從上圖其實我們就可以看到,我們可以通過資料庫連線把要執行的SQL語句傳送給MySQL資料庫。

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

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

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

但是大家既然跟著我開始學習了,從現在開始就要打破這種把資料庫當黑盒子的認知程度,要深入底層,去探索資料庫的工作原理以及生產問題的優化手段!

2、一個不變的原則:網路連線必須讓執行緒來處理

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

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

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

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

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

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

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

因此MySQL的工作執行緒接收到SQL語句之後,就會轉交給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語句進行一下拆解,拆解成以下幾個部分:

我們現在要從“users”表裡查詢資料
查詢“id”欄位的值等於1的那行資料
對查出來的那行資料要提取裡面的“id,age”三個欄位。

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

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

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

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

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

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

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

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

1、直接定位到“users”表中的“id”欄位等於1的一行資料,然後查出來那行資料的“id,age”三個欄位的值就可以了

2、先把“users”表中的每一行資料的“id,age”三個欄位的值都查出來,然後從這批資料裡過濾出來“id”欄位等於1的那行資料的“id,age”三個欄位

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

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

相當於他會告訴你,你應該按照一個什麼樣的步驟和順序,去執行哪些操作,然後一步一步的把SQL語句就給完成了。
我們來一起看看下面的圖:
在這裡插入圖片描述

6、呼叫儲存引擎介面,真正執行SQL語句

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

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

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

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

資料庫自己就是一個程式語言寫出來的系統而已,然後啟動之後也是一個程序,執行他裡面的各種程式碼,也就是我們上面所說的那些東西。所以對資料庫而言,我們的資料要不然是放在記憶體裡,要不然是放在磁碟檔案裡,沒什麼特殊的地方!

所以我們來思考一下,假設我們的資料有的存放在記憶體裡,有的存放在磁碟檔案裡,如下圖所示。
在這裡插入圖片描述
那麼現在問題來了,我們已經知道一個SQL語句要如何執行了,但是我們現在怎麼知道哪些資料在記憶體裡?哪些資料在磁盤裡?我們執行的時候是更新記憶體的資料?還是更新磁碟的資料?我們如果更新磁碟的資料,是先查詢哪個磁碟檔案,再更新哪個磁碟檔案?

是不是感覺一頭霧水

所以這個時候就需要儲存引擎了,儲存引擎其實就是執行SQL語句的,他會按照一定的步驟去查詢記憶體快取資料,更新磁碟資料,查詢磁碟資料,等等,執行諸如此類的一系列的操作,如下圖所示。
在這裡插入圖片描述
MySQL的架構設計中,SQL介面、SQL解析器、查詢優化器其實都是通用的,他就是一套元件而已。

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

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

7、執行器:根據執行計劃呼叫儲存引擎的介面

那麼看完儲存引擎之後,我們回過頭來思考一個問題,儲存引擎可以幫助我們去訪問記憶體以及磁碟上的資料,那麼是誰來呼叫儲存引擎的介面呢?

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

舉個例子,比如執行器可能會先呼叫儲存引擎的一個介面,去獲取“users”表中的第一行資料,然後判斷一下這個資料的“id”欄位的值是否等於我們期望的一個值,如果不是的話,那就繼續呼叫儲存引擎的介面,去獲取“users”表的下一行資料。

就是基於上述的思路,執行器就會去根據我們的優化器生成的一套執行計劃,然後不停的呼叫儲存引擎的各種介面去完成SQL語句的執行計劃,大致就是不停的更新或者提取一些資料出來

我們看下圖的示意
在這裡插入圖片描述