MySQL實戰 | 01-當執行一條 select 語句時,MySQL 到底做了啥?


原文鏈接:當執行一條 select 語句時,MySQL 到底做了啥?

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

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

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

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

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

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

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

下面開始正文。

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

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

mysql> select * from T where ID=10;

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

MySQL 的基本架構

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

MySQL 的基本架構

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

  • server 層中包含連接器,查詢緩存,分析器,優化器,執行器,大多數核心功能以及內置函數,存儲過程,觸發器,視圖等。
  • 存儲引擎層主要負責最終數據的存儲和提取,例如常用的存儲引擎 InnoDB、MyISAM 等。

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

MySQL 執行流程

1 連接

首先通過連接器連接到數據庫。

連接器的主要作用是建立連接,獲取用戶權限,維持連接,管理連接

連接的一般命令就是我們常用的登陸數據庫的命令:

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

命令執行后,若用戶名或者密碼不對,或者數據庫做了登錄 ip 限制,都會收到異常信息。

若登陸成功,那么就代表連接成功建立。

之后連接器會維持當前連接,接下來連接器會查詢出該用戶的權限,后面所有的操作都會基於該權限,即使操作過程中有其他進程修改了該用戶的權限。

連接完成后,若沒有任何操作,連接就處於休眠狀態,用命令 show processlist; 查看,就是 Sleep 狀態的進程:

睡眠狀態

當然,連接器不會讓你一直握着連接不動,若休眠時間超過 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’”。你覺得這個錯誤是在我們上面提到的哪個階段報出來的呢?

答案:分析器階段。

網友回答:

《高性能mysql》里提到解析器和預處理器。
解析器處理語法和解析查詢, 生成一課對應的解析樹。
預處理器進一步檢查解析樹的合法。比如: 數據表和數據列是否存在, 別名是否有歧義等。如果通過則生成新的解析樹,再提交給優化器。

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

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

最后就作者的問題,分析為什么是分析器,因為文章中說了詞法分析的時候會解析出查詢的表,列等等,所以此時就應該能知道表列的存在性。而且從我個人的拙見來看,如果先一步判斷出這種無法查詢的錯誤,避免后續執行,則可以避免無謂的性能開銷。而表列的數據較少,完全可以這里判斷。
當然,也可以在句法分析的步驟判斷,個人數據庫不太熟悉,只能從程序設計的角度考慮,望各位大佬真誠的評論

問:
丁老師,既然在鏈接階段已經通過權限表獲取了這個該連接所具有的權限,那么在執行階段再檢查一次的意義何在,謝謝!
作者回復:
執行器階段會碰到需要再判斷權限的情況,這時候讀內存中事先存好的權限,而這個權限是在連接器階段算出來存進去的

問:
長連接占用內存猛漲的情況下,您提供兩種解決方案,您傾向於在生產環境使用什么方案呢? 為什么呢? 或者你評價這兩種方案在生產環境有什么優劣呢?
作者回復:
5.7以上就建議用mysql_reset_connection 方法,低版本就定期斷開重連


另外,評論里多次提到了《高性能MySQL》一書,這里也為大家提供一個電子版,回復【005】可以獲取。

好了,第一篇,學習了好幾天,因為是工作黨,時間都是拼湊出來的,所以后續肯定跟不上專欄老師的節奏,不過會堅持的,大家一起加油干吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM