當執行一條select語句時,MySQL到底做了啥?

不正經程式設計師發表於2018-11-21

當執行一條select語句時,MySQL到底做了啥?

MySQL專欄學習系列,這個專欄我不會奢望多少人會看,只是記錄下來,希望可以幫到那些需要的人。

也許,你也跟我一樣,在遇到資料庫問題時,總時茫然失措,想重啟解決問題,又怕導致資料丟失,更怕重啟失敗,影響業務。

就算重啟成功了,對於問題的原因仍不知所以。

本文開始,記錄學習《MySQL實戰45講》專欄的過程。

也許有人會問,你記錄有什麼意義?直接看專欄不就行了嗎?你這不是啃別人的剩骨頭嗎?

是的,這個系列,我只是基於專欄學習,但是我會盡量從我的角度搞懂每一個知識點,遇到不懂得也會將知識點進行拆分。

我知道關注公眾號的小夥伴也有很多購買了這個專欄的,我希望大家都能夠利用好這個機會,把 MySQL 吃透!

看大家的反饋情況吧,若有需要,可以建個小群,大家互相討論學習!

下面開始正文。

當執行一條select語句時,MySQL到底做了啥?

大家或多或少都用過 MySQL,起碼 select 還是會用的吧,但是 select 執行後,MySQL 內部到底發生了什麼,你知道嗎?

比如,我們有個簡單的表 T,它有個 ID 欄位,那麼我們可以執行下面的語句:

mysql> select * from T where ID=10;

語句執行很簡單,但是具體到 MySQL 內部,其實是一個完整的執行流程。

MySQL 的基本架構

從下圖就可以清楚地看出 MySQL 的命令執行流程:

當執行一條select語句時,MySQL到底做了啥?

從該圖可以看出,MySQL 主要分為 server 層和儲存引擎層。

  • server 層中包含聯結器,查詢快取,分析器,最佳化器,執行器,大多數核心功能以及內建函式,儲存過程,觸發器,檢視等。

  • 儲存引擎層主要負責最終資料的儲存和提取,例如常用的儲存引擎 InnoDB、MyISAM 等。

好了,下面開始梳理一次完整的查詢流程。

若下面學習過程中懵逼了,建議隨時回來看這兩張圖!

當執行一條select語句時,MySQL到底做了啥?

1 連線

首先透過聯結器連線到資料庫。

聯結器的主要作用是建立連線,獲取使用者許可權,維持連線,管理連線

連線的一般命令就是我們常用的登陸資料庫的命令:

mysql -u$username -h$host -p$port -P

命令執行後,若使用者名稱或者密碼不對,或者資料庫做了登入 ip 限制,都會收到異常資訊。

若登陸成功,那麼就代表連線成功建立。

之後聯結器會維持當前連線,接下來聯結器會查詢出該使用者的許可權,後面所有的操作都會基於該許可權,即使操作過程中有其他程式修改了該使用者的許可權。

連線完成後,若沒有任何操作,連線就處於休眠狀態,用命令 show processlist;檢視,就是 Sleep 狀態的程式:

當執行一條select語句時,MySQL到底做了啥?

當然,聯結器不會讓你一直握著連線不動,若休眠時間超過 wait_timeout(預設為 8 小時),則會斷開當前連線。

若要再用,對不起,請重新連線~

長連線和短連線

其實這裡的長短連線不是 MySQL 層面的概念。

  • 長連線:長連線是相對於短連線來說的。長連線指在一個連線上可以連續傳送多個資料包,在連線保持期間,如果沒有資料包傳送,需要雙方發鏈路檢測包。我理解 MySQL 預設的超時時間 8 小時,就屬於一個長連結。


客戶端連線--建立 socket 認證連線--維護連線--資料傳輸--維護連線--資料傳輸.....-關閉連線


  • 短連線:是指通訊雙方有資料互動時,就建立一個連線,資料傳送完成後,則斷開此連線,即每次連線只完成一項業務的傳送。

客戶端連線--建立 socket 認證連線--維護連線--資料傳輸--關閉連線

長連線主要用於在少量客戶端與服務端的頻繁通訊,因為這時候如果用短連線頻繁通訊常會發生 Socket 出錯,並且頻繁建立 Socket 連線也是對資源的浪費。

專欄中老師是建議使用長連結的,因為建立連線的過程比較複雜,應該儘量減少建立連線的動作。

長連線的管理

使用長連線後,隨著連線數不斷增加,會導致記憶體佔用升高,因為 MySQL 在操作過程中會佔用記憶體來管理連線物件,只有等到連線斷開後才會釋放。

如果連線一直堆積,就會導致記憶體佔用過大,被系統強行殺掉,也就是會出現 MySQL 重啟。

如何解決這個問題?

1、定期斷開長連線;
2、MySQL 5.7+ 的版本中提供了 
mysql_reset_connection 來重新初始化連線資源,這時不需要重新連線,就可以將連線恢復到剛剛建立完時的狀態;

  • mysql_reset_connection

對於 mysql_reset_connection ,MySQL 官網的描述是這樣的:

將連線重置,清空連線狀態。 
類似於重新連線,但是不會關閉當前連線,也不會進行重新鑑權。

會產生如下影響:

1、會回滾所有活動事務,並重置自動提交模式;

2、會釋放所有的鎖表;

3、所有的臨時表會被關閉並清除;

4、Session 系統變數會被重新初始化為相應的全域性系統變數的值;

5、使用者自定義變數會丟失;

6、會釋放 Prepared statements;

7、HANDLER 變數會被關閉;

8、LAST_INSERT_ID() 函式的值會被重置為 0;

9、透過 GET_LOCK() 函式獲得的鎖會被釋放;

以上影響,翻譯自官方文件,有些可能不太準確,有興趣的可以到官網自行查閱原文。

  • 資料庫連線池?

另外,不少實際的應用框架中,大都使用連線池來維護連線數。

資料庫連線池,就是伺服器應用建立多個連線到資料庫,還沒有用的連線就放到連線池上,要的時候就向連線池取,這樣比沒有連線時再建立新的連線(TCP 建立連線是需要時間的)時要快很多,從而提高傳輸效率。

如 Spring 框架中,它實現了一個持久連線池,允許其他程式、客戶端來連線,這個連線池將被所有連線的客戶端共享使用,連線池可以加速連線,也可以減少資料庫連線,降低資料庫伺服器的負載。

2 查詢快取

快取,就是提前預備好的資料,資料庫查詢快取也是快取的一種。

在解析一個查詢語句之前,如果查詢快取是開啟的,那麼 MySQL 會優先檢查這個查詢是否命中查詢快取中的資料。

如果當前的查詢恰好命中了查詢快取,那麼在返回查詢結果之前 MySQL 會檢查一次使用者許可權。若許可權沒有問題,MySQL 會跳過所有其他階段(解析、最佳化、執行等),直接從快取中拿到結果並返回給客戶端。

這種情況下,查詢不會被解析,不用生成執行計劃,不會被執行。

快取哪裡來的?

查詢時如果沒有命中查詢快取,MYSQL 會判斷該查詢是否可以被快取,而且系統中還沒有對應的快取,則會將其結果寫入查詢快取。

mysql query cache 的內容為 select 的結果集,在記憶體中是以 HASH 結構來進行對映。

cache 會使用完整的 sql 字串做 key,並區分大小寫,空格等。即兩個 sql 必須完全一致才會導致 cache 命中。

快取何時失效?

在表的結構或資料發生改變時,查詢快取中的資料不再有效。

所以查詢快取適合有大量相同查詢的應用,不適合有大量資料更新的應用。

a) 一旦表資料進行任何一行的修改,基於該表相關 cache 立即全部失效,並且從緩衝區中移出;
b) 為什麼不做聰明一點判斷修改的是否 cache 的內容?因為分析 cache 內容太複雜,伺服器需要追求最大的效能。

快取可以提高查詢效率的?

當有大量的查詢和大量的修改時,cache 機制可能會造成效能下降。

因為每次修改會導致系統去做 cache 失效操作,這就會造成不小的開銷。

另外系統 cache 的訪問由一個單一的全域性鎖來控制,這時候大量的查詢將被阻塞,直至鎖釋放。

所以不要簡單認為設定 cache 必定會帶來效能提升。

參考:

https://www.cnblogs.com/duanxz/p/4385733.html

其實,在 8.0 版本開始,快取功能被直接刪除。

3 解析器

詞法解析

詞法分析的作用是將整個查詢分解為多個元素。

我們輸入的 MySQL 命令,不過是一串長長的字串,MySQL 的分析器會對其進行詞法解析。

select * from T where ID=1;

比如,上述語句是由多個字串和空格組成的一條 SQL 語句,MySQL 需要識別出裡面的字串分別是什麼,代表什麼。

MySQL 從你輸入的 select 這個關鍵字識別出來,這是一個查詢語句。

它也要把字串 T 識別成一個表名,把字串 ID 識別成一個列。

其實,大家也可以思考一下,若讓你手寫一個詞法分析的工具,你該如何實現呢?

語法分析

做完初步的詞法分析後,就要做語法分析

根據詞法分析的結果,語法分析器會根據語法規則,判斷你輸入的這個 SQL 語句是否滿足 MySQL 語法。

如果你的語句不對,就會收到 You have an error in your SQL syntax 的錯誤提醒。

解析器的最終執行結果就是解析樹,提供給最佳化器使用。

4 最佳化器

當你提交一個查詢的時候,MySQL會分析它,看是否可以做一些最佳化使處理該查詢的速度更快。

最佳化器到底幹啥的?

MySQL 的最佳化器有幾個重要任務:

1、選擇最合適的索引;

2、選擇表掃還是走索引;

3、選擇表關聯順序;

4、最佳化 where 子句;

5、排除管理中無用表;

6、決定 order by 和 group by 是否走索引;

7、嘗試使用 inner join 替換 outer join;

8、簡化子查詢,決定結果快取;

9、合併試圖;

MySQL 查詢最佳化器有幾個目標,但是其中最主要的目標是儘可能地使用索引,並且使用最嚴格的索引來消除儘可能多的資料行。

最佳化器試圖排除資料行的原因在於它排除資料行的速度越快,那麼找到與條件匹配的資料行也就越快。如果能夠首先進行最嚴格的測試,查詢就可以執行地更快。

最佳化器是如何工作的?

到底最佳化器是如何進行選擇的?如果每個點都展開,那都需要很長的篇幅,我再網上翻閱了一些資料,看得也是雲裡霧裡,後面結合專欄老師的講解再學習吧。

這裡舉幾個最佳化的示例:

  • 示例 1

假設你的查詢檢驗了兩個資料列,每個列上都有索引:

SELECT col3 FROM mytable
WHERE col1 = 'value1' AND col2 = 'value2';

假設 col1 上的測試匹配了 900 個資料行,col2 上的測試匹配了 300 個資料行,而同時進行的測試只得到了 30 個資料行。

先測試 col1 會有 900 個資料行,需要檢查它們找到其中的 30 個與 col2 中的值匹配記錄,其中就有 870 次是失敗了。

先測試 col2 會有 300 個資料行,需要檢查它們找到其中的 30 個與 col1 中的值匹配的記錄,只有 270 次是失敗的,因此需要的計算和磁碟 I/O 更少。

其結果是,最佳化器會先測試 col2,因為這樣做開銷更小。

  • 示例 2

儘可能地讓索引列在比較表示式中獨立。如果你在函式呼叫或者更復雜的算術表示式條件中使用了某個資料列,MySQL就不會使用索引,因為它必須計算出每個資料行的表示式值。

比如,下面的 WHERE 子句顯示了這種情況。它們的功能相同,但是對於最佳化目標來說就有很大差異了:

WHERE mycol < 4 / 2
WHERE mycol * 2 < 4

對於第一行,最佳化器把表示式 4/2 簡化為 2,接著使用 mycol 上的索引來快速地查詢小於 2 的值。

對於第二個表示式,MySQL 必須檢索出每個資料行的 mycol 值,乘以 2,接著把結果與 4 進行比較。在這種情況下,不會使用索引。資料列中的每個值都必須被檢索到,這樣才能計算出比較表示式左邊的值。

最佳化器的內容還可以有很多,這個專欄老師說後續會還有講。

5 執行器

下面就到了最終的執行階段,執行開始之前,會先判斷是否有操作許可權,若沒有,會丟擲相關異常。

如果有許可權,就開啟表繼續執行。開啟表的時候,執行器就會根據表的引擎定義,去使用這個引擎提供的介面。

比如我們這個例子中的表 T 中,ID 欄位沒有索引,那麼執行器的執行流程是這樣的:

1、呼叫 InnoDB 引擎介面取這個表的第一行,判斷 ID 值是不是 10,如果不是則跳過,如果是則將這行存在結果集中;
2、呼叫引擎介面取下一行,重複相同的判斷邏輯,直到取到這個表的最後一行。
3、執行器將上述遍歷過程中所有滿足條件的行組成的記錄集作為結果集返回給客戶端。

至此,這個語句就執行完成了。

對於有索引的表,執行的邏輯也差不多。第一次呼叫的是取滿足條件的第一行這個介面,之後迴圈取滿足條件的下一行這個介面,這些介面都是引擎中已經定義好的。

可以看出,是否有索引,執行效率區別還是很大的,沒有索引需要取出所有資料,一個個進行比較;而有索引則是直接取滿足條件的資料;

課後題目 & 評論區精華

課後題目

如果表 T 中沒有欄位 k,而你執行了這個語句 select * from T where k=1, 那肯定是會報“不存在這個列”的錯誤: “Unknown column ‘k’ in ‘where clause’”。你覺得這個錯誤是在我們上面提到的哪個階段報出來的呢?

評論區:

答1

《高效能mysql》裡提到解析器和前處理器。

解析器處理語法和解析查詢, 生成一課對應的解析樹。

前處理器進一步檢查解析樹的合法。比如: 資料表和資料列是否存在, 別名是否有歧義等。如果透過則生成新的解析樹,再提交給最佳化器。

答2

文中講解分析器階段時提到,MySQL 從你輸入的"select"這個關鍵字識別出來,這是一個查詢語句。它也要把字串“T”識別成“表名 T”,把字串“ID”識別成“列 ID”。

所以應該是分析器。

答3

我猜測應該在分析階段,根據文章介紹分析器的作用是讓mysql知道你要做什麼,對語法的分析應該是第一部,語法詞法分析完成後應該是解析這條sql到底要執行什麼操作,插入還是更新還是建表還是查詢,這時mysql應該已經知道你想操作那個表而這個表存不存在,從而才能匹配不同的最佳化器類

答4

最後就作者的問題,分析為什麼是分析器,因為文章中說了詞法分析的時候會解析出查詢的表,列等等,所以此時就應該能知道表列的存在性。而且從我個人的拙見來看,如果先一步判斷出這種無法查詢的錯誤,避免後續執行,則可以避免無謂的效能開銷。而表列的資料較少,完全可以這裡判斷。

當然,也可以在句法分析的步驟判斷,個人資料庫不太熟悉,只能從程式設計的角度考慮,望各位大佬真誠的評論

丁老師,既然在連結階段已經透過許可權表獲取了這個該連線所具有的許可權,那麼在執行階段再檢查一次的意義何在,謝謝!

執行器階段會碰到需要再判斷許可權的情況,這時候讀記憶體中事先存好的許可權,而這個許可權是在聯結器階段算出來存進去的

長連線佔用記憶體猛漲的情況下,您提供兩種解決方案,您傾向於在生產環境使用什麼方案呢? 為什麼呢? 或者你評價這兩種方案在生產環境有什麼優劣呢?

5.7以上就建議用mysql_reset_connection 方法,低版本就定期斷開重連


好了,第一篇,學習了好幾天,也不可能做到盡善盡美,不足的地方,希望大家可以在留言區交流。

因為是工作黨,時間都是拼湊出來的,所以後續肯定跟不上專欄老師的節奏,不過會堅持的,大家一起加油幹吧!

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559358/viewspace-2220949/,如需轉載,請註明出處,否則將追究法律責任。

相關文章