在Mysql中執行一條SQL,會經歷什麼?

騰訊IVWEB團隊發表於2020-03-20

背景

我們都經常使用Mysql作為資料庫來儲存與查詢較常用的資料。當我們輸入一行如SELECT * FROM table_name WHERE id=26這樣的語句之後,Mysql如果正確執行的情況下,會輸出你想要的資訊。

那麼,在你輸入這行語句之後,一直到它顯示出你想要的資訊,這中間Mysql都經歷了什麼呢?這篇文章會簡單聊一下這個事情。

Mysql基本架構圖

我們先看下Mysql的一個較整體的架構圖。

接下來我會以具體的SQL語句為例,詳細的敘述從你在客戶端輸入了這個語句之後,到它返回你想要的資訊,這中間具體經歷了什麼。

在Mysql中執行一條SQL,會經歷什麼?

客戶端

所謂客戶端,即是我們登入與操作Mysql所使用的終端。我們都是在客戶端對Mysql進行操作的,無論是輸入連線資料庫的資訊,還是輸入查詢某個表的SQL,或者是收到Mysql返回給我們的查詢資訊,這些都是在客戶端完成的。

聯結器

使用者資訊驗證

我們在一個客戶端跟前,想要使用Mysql資料庫,那麼第一步就是要先連線上你要使用的資料庫。

我們都知道,我們要輸入命令mysql -h$ip -P$port -u$username -p

之後客戶端會要求我們輸入密碼。再之後,如果我們輸入的資訊都沒有問題了,我們就進入Mysql的操作介面了。

如果我們輸入的資訊有問題,就會收到客戶端返回的報錯資訊。比如我們將密碼輸入錯誤了,這時就會收到"Access denied for user"這樣的報錯資訊。

那麼,這中間聯結器具體做了什麼呢?

首先,聯結器會拿著我們輸入的IP和埠,去做最經典的TCP握手,握手如果都失敗了,那就自然沒有後續了,直接返回相應的報錯資訊。

如果握手成功了,此時則會去驗證我們輸入的使用者名稱和密碼,驗證失敗則同樣會返回相應的報錯資訊。

使用者許可權獲取

如果使用者名稱密碼也沒有問題,接下來聯結器則會取出許可權表讀取該使用者相應的許可權資料。使用者跟著所做的所有操作,都基於此時讀取到的使用者許可權。

許可權表共有4個:user, db, tables_priv, columns_priv

當使用者通過許可權驗證,進行許可權分配時,按照user, db, tables_priv, columns_priv的順序進行分配。即先檢查使用者的全域性許可權表user,如果user中對應的許可權為Y,則此使用者對所有資料庫的許可權都為Y,將不再檢查剩餘3個表;如果為N,則到db表中檢查此使用者對應的具體資料庫,並得到db中為Y的許可權;如果db中為N,則檢查tables_priv中此資料庫對應的具體表,取得表中的許可權Y;如果為N,則到columns_priv中檢查具體的列。

這也就意味著,當我們修改了某個使用者的使用者許可權,只有到下一次該使用者登入(建立新的連線)時,才會影響到該使用者。

連線與等待超時

我們可以通過show processlist來檢視當前所有的使用者連線及其行為。

在Mysql中執行一條SQL,會經歷什麼?
Command中的欄位顯示該使用者目前的狀態,此時這個使用者是查詢狀態。

但若Command顯示的狀態是Sleep,那麼說明該使用者當前在等待狀態。若等待超過了一段時間,則聯結器會自動斷開。

該超時時間由wait_timeout變數控制,可以通過show global variables like 'wait_timeout'來檢視。

在Mysql中執行一條SQL,會經歷什麼?
mysql預設為28800秒,即8小時。

長連線與短連線

所謂長連線,即使用者的持續操作使用的都是同一個連線,連線在一段時間內長時間建立。

所謂短連線,即使用者每做幾次操作則斷開,再下次操作時再進行連線。

長連線的優點是,在持續操作時,可以節省很多建立連線所需要消耗的時間。但是長連線所要儲存的臨時資料都在連線物件中,長時間積累,會導致系統記憶體溢位,具體表現 為Mysql異常重啟。

短連線的優缺點與長連線相反,雖然不用擔心記憶體溢位的問題,但短連線在持續操作的情況下多次連線,連線消耗很多時間,整體操作效率會很低。

快取器

聯結器連線完成的下一步就是快取器的快取查詢,如果我們需要對一張靜態表(不常更新)經常做查詢操作,那麼可能會用到快取器。

快取器中使用的是key-value的儲存形式,key值儲存的是查詢語句,value值儲存的是對應結果。

要注意的是,只要該表做了一次更新操作,那麼該表對應的快取就會全部被清理。因此使用場景並不多。

所以當前快取器的使用較少。我們可以通過query_cache_type來檢視快取器是否開啟。

在Mysql中執行一條SQL,會經歷什麼?
現在一般都是預設關閉的狀態。且Mysql從8.0版本會開始徹底棄用該功能。

分析器

假設我們不使用快取器,或者通過快取器沒有命中SQL語句。

那麼聯結器做連線操作之後,接下來我們就輸入了一個查詢語句,比如:SELECT host FROM mysql.user LIMIT 1

而分析器做的事情就是對你輸入的語句做 “詞法分析”“語法分析”

所謂 “詞法分析” ,就是判斷每一個你輸入的詞,比如分析器首先會判斷出你輸入的第一個詞是“SELECT”,第二個詞你輸入了“host”,等等。

“語法分析” 則是跟在 “詞法分析” 之後,就是依據你輸入的這些詞來判斷你輸入的是否符合語法規則。

假如符合語法規則,則會順利進行下去並返回相應資訊。

在Mysql中執行一條SQL,會經歷什麼?
假如不符合語法規則,則分析器會返回報錯資訊給客戶端。
在Mysql中執行一條SQL,會經歷什麼?
具體出錯的地方,一般都是跟在use near之後,我們看這裡就能知道語法錯誤出在了哪一塊。

優化器

在分析器工作結束後,如果語法有問題,那麼就會直接返回報錯資訊,且不繼續向下執行。

若語法正確,那麼,則會到優化器部分的工作。優化器顧名思義,就是對該語句的執行做優化。

比如,在一個語句查詢某個表時,該表可能有多個索引,此時使用哪個索引會使語句的執行效率最高?這就是優化器要做的事情。

再比如,執行語句select * from t1 join t2 on t1.ID=1 and t2.ID=2

該語句執行時,是先從t1表中找到ID=1的行關聯到t2表之後,再從t2表中查詢ID=2的行。

還是先從t2表中找到ID=2的行關聯到t1表之後,再從t1表中查詢ID=1的行。

兩種執行順序可能就導致執行效率的不同,怎樣選擇執行順序會提高執行效率,這也是優化器要做的事情。

執行器

在上述步驟完成之後,就輪到執行器去執行具體的語句了。

例如語句:select * from mysql.tables_priv

在執行器做具體的語句執行之前,會對該表的操作許可權進行驗證,驗證失敗則返回許可權錯誤的報錯。如下:

在Mysql中執行一條SQL,會經歷什麼?
而實際上,許可權驗證不僅僅在執行器這部分會做,在分析器之後,也就是知道了該語句要“幹什麼”之後,也會先做一次許可權驗證。叫做precheck。

而precheck是無法對執行時涉及到的表進行許可權驗證的,比如使用了觸發器的情況。因此在執行器這裡也要做一次執行時的許可權驗證。

如果驗證成功,那麼則會使用該表對應的儲存引擎的介面,繼續執行語句。 最後將成功執行的結果返回給客戶端。

總結

簡單來說,一條SQL語句在Mysql中執行,一共會經歷四步(算上連線Mysql),分別是連線、分析、優化與執行。每一步都會精確執行,如果發現有問題就會返回給客戶端相應的報錯。只有每一步都正確執行,最終才會在客戶端得到你想要查詢或操作的結果。

相關文章