一條查詢SQL在MySQL中是怎麼執行的

Python實戰社羣

Java實戰社羣

長按識別下方二維碼,按需求添加

掃碼關注添加客服

進Python社羣▲

掃碼關注添加客服

進Java社羣

作者丨故里

來源丨故里學Java

平時我們使用的數據庫,看到的通常是一個整體,比如我們執行一條查詢SQL,返回一個結果集,卻不知道這條語句在MySQL內部是如何執行的,接下來我們就來簡單的拆解一下MySQL,看看MySQL是由哪些“零件”組成的,在這個過程中逐步的揭開MySQL的面紗,對MySQL有個深入的理解。這樣在我們以後遇到MySQL的一些異常或者問題的時候,就可以快速定位問題並解決問題。

下邊通過一張圖來看一下SQL的執行流程,從中可以清楚的看到SQL語句在MySQL的各個功能模塊中執行的過程。

從上圖來看,MySQL大體上可以分爲Server層和存儲引擎層兩部分。

Server層:包含連接器、查詢緩存、分析器、優化器、執行器等,涵蓋了MySQL的大部分核心服務功能,以及所有的內置函數,所有的跨存儲引擎的功能都在這一層實現,比如存儲過程、觸發器、視圖等,

存儲引擎層:負責數據的存儲和提取,其架構模式是插件式的,支持InnoDB、MyISAM、Memory等多個存儲引擎。其中最常用的存儲引擎是InnoDB,從MySQL5.5.5版本開始就成爲了默認的存儲引擎。也就是在創建表的時候,如果不指定存儲引擎類型,默認就是使用InnoDB,如果需要使用別的存儲引擎,在創建表的時候在create table語句中使用engine = MyISAM,來指定使用M有ISAM引擎創建表。不同的存儲引擎的表數據存取方式不同,支持的功能也不相同,以後我們再慢慢分析。

從圖中我們可以看到Server層由多個組件,從連接器開始到執行器,接下來我們使用一條簡單的查詢語句,來依次分析每個組件的作用。

select * from T where ID = 10;

連接器

第一步,會先連接到這個數據庫上,這個時候首先遇到的是連接器。連接器是負責跟客戶端建立連接、獲取權限、維持和管理連接。一般連接命令是這樣寫的:

mysql -h$ip -P$port -u$user -p

輸入命令之後,就需要在交互對話中輸入密碼,密碼也可以直接寫在-p後面,但是這種操作一般是開發過程中,連接生產服務器不建議這樣做,因爲可能會導致密碼泄露。

連接命令中的mysql是客戶端工具,用來和服務端建立連接,在完成經典的TCP握手後,連接器就開始認證身份,這個時候用到的就是輸入的用戶名和密碼。

  • 用戶名或密碼不對時,就會報一個“Access denied for user”的錯誤,然後客戶端程序結束執行。

  • 如果用戶名密碼認證通過,連接器會到權限表裏面查出你這個用戶名的權限,之後這個連接裏面的權限判斷邏輯,都將依賴於此時讀到的權限,建立連接後權限就確定下來了,如果這個時候使用管理員賬號修改了這個用戶的權限,也是需要下次重新連接時生效。

連接完成後,如果沒有後續操作,這個連接就處於空閒狀態,可以使用show processlist命令查詢,下圖中的Command列顯示爲Sleep的這一行,就表示現在系統中有一個空閒連接。

客戶端如果長時間處於空閒狀態,連接器就會自帶將它斷開連接,這個時間由參數wait_timeout控制,默認值是8小時。

如果在斷開連接後,客戶端再次發送請求的話,就會收到一個錯誤提示:“Lost connection to MySQL server during query”。這個時候如果需要繼續操作數據庫,就需要重新連接然後再執行請求。

數據庫裏面有兩種連接分別是長連接和短連接,長連接是指連接成功後,如果客戶端持續有請求,則一直使用同一個連接,短連接則是指每次執行完很少的幾次查詢就斷開連接,下次查詢再重新建立一個連接。因爲建立連接的過程通常比較複雜,所以建議儘量減少建立連接的動作,也就是儘量使用長連接而不是短連接。

當我們全部使用長連接後,會發現有時候MySQL專用內存漲的特別快,這是因爲MySQL在執行過程中臨時使用的內存是管理在連接對象裏面的,這些資源會在連接斷開的時候才釋放,所以長時間使用長連接累計下來,可能導致內存佔用太大,被系統強行殺掉,也就是我們有時候看到的MySQL異常重啓。

這個問題也不是不能解決的,常用的方案主要有以下兩種:

  1. 定期斷開長連接。使用一段時間,或者程序裏面判斷執行過一個佔用內存的大查詢後,斷開連接,之後要查詢再重連。

  2. 如果MySQL版本是5.7或以上版本,可以在每次執行一個比較大的操作後,通過執行mysql_reset_connection來重新初始化連接資源。這個過程不需要重連和重新做權限校驗,但是會將連接恢復到剛創建完成的狀態。

查詢緩存

在建立完成後,就可以執行select語句了,執行邏輯就會來到查詢緩存。MySQL拿到一個查詢請求後,會先到緩存查查看看,如果之前執行過的語句就會將執行過的語句和結果以key-value對的形式,被直接存放在內存中,key是查詢語句,value是結果。如果查詢語句在緩存中可以查到這個key,就直接把結果返回給客戶端。如果語句不在緩存中,就會繼續執行後邊的階段。執行完成後,將執行結果存入緩存中。

但是,大部分情況下都不建議使用查詢緩存,這是因爲查詢緩存往往弊大於利。

查詢緩存的失效非常頻繁,只要對錶做一次更新操作,這個表上所有的查詢緩存都會被清空,因此經常會出現剛把結果放入緩存還沒使用,就被一個更新清空了,所以對於更新很頻繁的數據庫來說,查詢緩存的命中率很低。除非是很久才更新一次的數據表,比如系統配置表,那這張表上的查詢才適合使用查詢緩存。

MySQL提供了query_cache_type參數來設置是否查詢緩存,將該參數設置成DEMAND這樣對於默認的SQL語句都不使用查詢緩存,如果確定需要使用查詢緩存的語句,可以用SQL_CACHE來顯式指定,如下:

mysql> select SQL_CACHE * from T where ID = 2;

如果你使用的MySQL8.0版本就不用考慮這個問題,因爲8.0版本開始徹底的沒有這個功能了。

分析器

接着上面一步,如果沒有命中查詢緩存,就開始真的執行語句了,首先MySQL需要知道你要做什麼,會對SQL語句進行解析。

分析器會先做“詞法分析”,你輸入的SQL語句中由多個字符串和空格組成,MySQL需要識別出裏面的字符串分別是什麼,代表什麼。如上邊的SQL語句,MySQL從你輸入的select關鍵字識別出來,這是查詢語句,它也會把字符串T識別成表名“T“,把字符串ID識別成”列ID“。

做完了這些識別之後,就會做”語法分析“,根據詞法分析的結果,語法分析會根據語法規則,判斷輸入的SQL語句是否滿足MySQL的語法要求。

如果SQL語句有問題,就會有”You have an error in your SQL syntax“的錯誤提醒

mysql> elect * from t where ID=1;


ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1

一般語法錯誤會提示第一個出現錯誤的位置,user near緊接的內容就是報錯內容相關。

優化器

經過了分析器,MySQL就知道你要做什麼了,在執行之前,還要經過優化器處理。

優化器是在表裏有多個索引的時候,決定使用哪個索引;或者在一個語句中有多表關聯的時候,決定各個表的連接順序。如下的語句:

mysql> select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;
  • 既可以先從t1表裏取出c=10的記錄的ID值,再根據ID值關聯到表t2,再判斷t2表裏的值是否等於20

  • 也可以先從t2表裏取出d = 20的記錄ID值,再根據ID關聯到t1表,再判斷t1表裏面c1 的值是否等於10

這兩個執行的邏輯結果是一樣的,但是執行效率是不同的,優化器在這裏的作用就是決定選擇哪一種方案。優化器遵循的原則:儘可能掃描少的數據庫行記錄。

優化器階段完成後,這個SQL語句的執行方案就確定下來了,進入執行階段。

執行器

通過前面幾步操作,MySQL已經知道了你要做什麼,也優化了做的方式,就進入執行器階段,開始執行語句。開始執行的時候,要先判斷一下你對這個表有沒有執行查詢的權限,如果沒有,就會返回沒有權限的錯誤。這裏還有一個細節,如果在第二步查詢緩存的時候命中緩存,會在緩存返回結果的時候做權限校驗。查詢也會在優化器之前調用precheck驗證權限。

主要這裏是對錶的權限進行校驗,而連接器是驗證用戶的身份。

如果有權限,就打開表繼續執行,打開表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的接口。如我們這個例子的表T中,ID字段沒有添加索引,那麼執行流程如下:

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

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

  3. 執行器將上述遍歷過程中所有滿足條件的行組成一個結果集返回給客戶端。

到這裏,這個查詢SQL就執行完成了。

對於有索引的表,執行的邏輯大同小異,第一次調用的是“取滿足條件的第一行”這個接口,然後循環取“滿足條件的下一行”這個接口,這些接口都是引擎中定義好的。

在數據庫的慢查詢日誌中可以看到一個rows_examined的字段,表示這個語句執行過程中掃描了多少行,這個值是在執行器每次調用引擎的時候累加的,有時候執行器調用一次,在引擎內部掃描了多行,隱藏引擎掃描行數跟rows_examined並不完全相同。

到這裏,對於一個SQL語句完整執行流程各個階段有了初步認識,對於我們後續深入學習MySQL有個基礎,最後分享一個小故事。

連接器:門衛,想進請出示准入憑證(工牌、邀請證明一類)。“你好,你是普通員工,只能進入辦公大廳,不能到高管區域”此爲權限查詢。

分析器:“您需要在公司裏面找一張頭髮是黑色的桌子?桌子沒有頭髮啊!臣妾做不到”

優化器:“要我在A B兩個辦公室找張三和李四啊?那我應該先去B辦公室找李四,然後請李四幫我去A辦公室找張三,因爲B辦公室比較近且李四知道張三具體工位在哪”

執行器:“好了,找人的計劃方案定了,開始行動吧,走你!糟糕,剛門衛大哥說了,我沒有權限進B辦公室”

程序員專欄 掃碼關注填加客服 長按識別下方二維碼進羣

近期精彩內容推薦:  

 幾句話,離職了

 中國男性的私密數據大賞,女生勿入!

 爲什麼很多人用“ji32k7au4a83”作密碼?

 一個月薪 12000 的北京程序員的真實生活 !


在看點這裏好文分享給更多人↓↓

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