MySQL 查詢執行的流程

1. 查詢執行路徑

在這裏插入圖片描述
上圖爲 MySQL 查詢執行的流程概覽,其主要流程爲以下幾步:

  1. 客戶端將查詢請求發送到服務端
  2. 服務端首先檢查查詢緩存是否開啓,如果開啓並且當前查詢命中緩存就從緩存中返回結果,否則進行下一步
  3. 服務端解析 SQL 語句並進行預處理,再由查詢優化器生成對應的執行計劃
  4. 執行引擎根據執行計劃調用存儲引擎 API 執行查詢
  5. 服務端將結果發送回客戶端

2. 查詢執行步驟詳解

2.1 客戶端發起查詢請求

  1. 首先需要清楚 MySQL 客戶端與服務器端的通信協議是 半雙工的,就是在任何時刻要麼由客戶端向服務器端發送數據,要麼由服務器端向客戶端發送數據,這兩個動作不能同時發生。這種協議讓 MySQL 通信簡單快速,但是缺點在於一旦一端開始發送數據另一端要接收完所有信息才能響應,這樣就沒法進行流量控制

  2. 客戶端發起查詢時首先用一個單獨的數據包將查詢發送到服務器,因爲半雙工機制一旦客戶端發送了查詢,剩下的就是等待結果

    如果一個查詢過大,有時會出現"MySQL server has gone away"的錯誤,原因可能就是傳送的數據太大導致連接斷開了,可以通過命令SHOW VARIABLES LIKE "max_allowed_packet" 查看服務器所允許傳送的最大數據,該值可在my.ini文件裏配置

  3. 服務器響應的數據通常很多,由多個數據包組成。服務器發送響應的時候客戶端必須接收完整的結果集,不能只提取幾行數據後要求服務器停止發送剩下的數據,所以使用LIMIT來限制所需要的數據行數有時是必要的

    MySQL 需要所有數據都已經發送給客戶端之後才能釋放這條查詢所佔用的資源,所以客戶端接收全部結果並緩存可以減少服務器端的壓力,讓服務器端能夠早點釋放資源。與之相對的,則是內存壓力轉移到了客戶端,由客戶端負責緩存數據,並從內存中按需取用

查詢狀態

每個 MySQL 連接在任意時間點都有一個狀態來標識正在進行的事情,可以使用 SHOW FULL PROCESSLIST 命令來查看哪些線程正在運行,及其查詢狀態,Command 列顯示了狀態

mysql> SHOW FULL PROCESSLIST;
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+
| Id | User            | Host      | db   | Command | Time  | State                  | Info                  |
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+
|  4 | event_scheduler | localhost | NULL | Daemon  | 86599 | Waiting on empty queue | NULL                  |
|  9 | root            | localhost | NULL | Query   |     0 | starting               | SHOW FULL PROCESSLIST |
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+

MySQL 服務端連接狀態 說明
Sleep 線程正在等待客戶端,以向它發送一個新請求
Query 線程正在執行查詢或往客戶端發送數據
Locked 該查詢所需的資源被其它查詢鎖定,通常表示在等待表鎖
Analyzing and statistics 線程正在收集存儲引擎的統計信息,並生成查詢的執行計劃
Copying to tmp table [on disk] 正在執行查詢,並將結果集都複製到一張臨時表中。如果有on disk表示臨時結果集合大於tmp_table_size,線程把臨時表從存儲器內部放到磁盤上
Sending data 線程正在爲SELECT語句處理行,同時正在向客戶端發送數據
Sorting for group 線程正在進行分類,以滿足 GROUP BY要求
Sorting for order 線程正在進行分類,以滿足 ORDER BY要求

2.2 查詢緩存

在解析 SQL 語句之前,如果開啓了查詢緩存,那麼 MySQL 會檢查查詢緩存,進行大小寫敏感的哈希查找。查找規則比較嚴格,即使查詢和緩存中的查詢只有一個字節的差異,也表示不匹配,查詢就會進入下一階段

MySQL 查詢緩存保留了查詢返回給客戶端的完整結果,當緩存命中的時候服務器會先檢查權限,檢查通過後直接返回保存的結果,跳過解析、優化和執行步驟。查詢緩存會跟蹤查詢使用過的每個表,如果這些表數據發生了改變,那麼和這個表相關的緩存數據就失效了

MySQL 判定緩存命中的方法很簡單:緩存存放在一個引用表中,通過一個哈希值進行引用。這個哈希值包含了查詢本身、要查詢的數據庫、客戶端協議版本等一些可能會影響返回結果的信息,以便正確地命中緩存。需注意,當查詢語句中有一些不確定的數據時,則不會被緩存,例如包含函數 NOW()、CURRENT_DATE()等。另外查詢緩存是在完整的 SELECT 語句基礎上的,只在剛剛收到 SQL 語句的時候檢查,所以子查詢和存儲過程都無法使用查詢緩存

2.3 SQL 解析與預處理

MySQL 根據關鍵字對 SQL 語句進行解析,將查詢分解成一個個標識,從而生成一棵對應的解析樹

  1. 解析器負責保證查詢中的標識都是有效的,它將使用 MySQL 語法規則對解析樹進行驗證和解析,例如驗證是否使用錯誤的關鍵字、使用關鍵字的順序是否正確,或者字符串上面的引號有沒有閉合等
  2. 預處理器則根據一些MySQL規則進一步檢查解析樹是否合法,例如檢查數據表和數據列是否存在,還會解析字段名稱和別名,看看它們是否有歧義。最後,預處理器會檢查權限

2.4 查詢優化器優化

查詢優化器負責把解析樹變成執行計劃,一個查詢通常可以有很多種執行方式,優化器的任務就是找到最好的方式

MySQL 的查詢優化器基於成本決策,它將嘗試預測一個查詢使用某種執行計劃的成本,並選擇其中成本最小的一個,可以通過命令SHOW STATUS LIKE "Last_query_cost"得知當前會話中 MySQL 計算的當前查詢的成本

MySQL的查詢優化爲了生成一個最優的執行的計劃使用了很多優化策略,它們可以分爲兩種,靜態優化和動態優化

  • 靜態優化
    直接對解析樹進行分析,並完成優化。例如優化器可以通過一些簡單的代數變換將 where 條件轉換成另一種等價形式。靜態優化不依賴於特別的數值,如 where 條件中帶入的一些常數等。靜態優化在第一次完成後就一直有效,即使使用不同的參數重複查詢也不會變化,可以認爲是一種編譯時優化
  • 動態優化
    動態優化與查詢的上下文有關,也可能和很多其他因素有關,例如 where 條件中的取值、索引中條目對應的數據行數等,這些每次查詢的時候都需要重新評估,可以認爲是運行時優化

以下是一些MySQL能夠處理的優化類型:

優化類型 說明
重新定義關聯表的順序 數據表的關聯並不總是按照在查詢中指定的順序進行
將外連接轉化成內連接 並不是所有的outer join語句都必須以外連接的方式執行。諸多因素,例如where條件、庫表結構都可能會讓外連接等價於一個內連接。MySQL能夠識別這點並重寫查詢,讓其可以調整關聯順序,以便適應其它的優化,比如排序
使用等價變換規則 MySQL可以使用一些等價變換來簡化並規範表達式。它可以合併和減少一些比較,還可以移除一些恆成立和一些恆不成立的判斷。例如:(5=5 and a>5)將被改寫爲a>5。類似的,如果有(a< b and b=c)and a=5,則會被改寫爲 b>5 and b=c and a=5
優化count()、min()和max() 索引和列是否爲空通常可以幫助MySQL優化這類表達式。例如,要找到一列的最小值,只需要查詢對應B-tree索引最左端的記錄,MySQL可以直接獲取索引的第一行記錄。在優化器生成執行計劃的時候就可以利用這一點,在B-tree索引中,優化器會講這個表達式最爲一個常數對待。類似的,如果要查找一個最大值,也只需要讀取B-tree索引的最後一個記錄。如果MySQL使用了這種類型的優化,那麼在explain中就可以看到“select tables optimized away”。從字面意思可以看出,它表示優化器已經從執行計劃中移除了該表,並以一個常數取而代之
預估並轉化爲常數表達式 MySQL 檢測到一個表達式可以轉化爲常數的時候,就會一直把該表達式當作常數進行優化處理,數學表達式就是一個典型的例子
覆蓋索引掃描 當索引中的列包含所有查詢中需要使用的列的時候,MySQL就可以使用索引返回需要的數據,而無需查詢對應的數據行
子查詢優化 MySQL在某些情況下可以將子查詢轉換成一種效率更高的形式,從而減少多個查詢多次對數據進行訪問
提前終止查詢 在發現已經滿足查詢需求的時候,MySQL總是能夠立即終止查詢,一個典型的例子就是當使用了limit子句的時候。除此之外,MySQL還有幾種情況也會提前終止查詢,例如發現了一個不成立的條件,這時MySQL可以立即返回一個空結果
等值傳播 如果兩個列都值通過等式關聯,MySQL 能夠將其中一個列的 where 條件傳遞到另一列上
列表in()的比較 MySQL將in()列表中的數據先進行排序,然後通過二分查找的方式來確定列表中的值是否滿足條件,這是一個o(log n)複雜度的操作,等價轉換成or的查詢的複雜度爲o(n),對於in()列表中有大量取值的時候,MySQL的處理速度會更快

2.5 查詢執行引擎

在查詢優化階段,MySQL 將生成查詢對應的執行計劃,MySQL 的查詢執行引擎則根據這個執行計劃給出的指令逐步執行來完成整個查詢。在這個過程中,有大量的操作需要通過調用存儲引擎實現的被稱爲“handler API”的接口來完成

MySQL在優化階段就爲每個表創建了一個 handler 實例,優化器根據這些實例的接口可以獲取表的相關信息,包括表的所有列名、索引統計信息等

2.6 結果返回

查詢執行的最後一個階段是將結果返回給客戶端,即使查詢不需要返回結果給客戶端,MySQL仍然會返回這個查詢的一些信息,例如查詢影響到的行數。如果查詢可以被緩存,那麼MySQL在這個階段會將結果存放到查詢緩存中

MySQL 將結果返回客戶端是一個增量、逐步返回的過程,例如在關聯表操作時,一旦服務器處理完最後一個關聯表,開始生成第一條結果時,MySQL 就可以開始向客戶端逐步返回結果集了。這樣處理有兩個優點:

  1. 服務器無需存儲太多的結果,也就不會因爲要返回太多的結果而消耗太多的內存
  2. 這樣的處理讓 MySQL 客戶端可以在第一時間獲得返回的結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章