在上一篇文章《MySQL常見加鎖場景分析》中,咱們聊到行鎖是加在索引上的,可是複雜的 SQL 每每包含多個條件,涉及多個索引,找出 SQL 執行時使用了哪些索引對分析加鎖場景相當重要。mysql
好比下面這樣的 SQL:sql
mysql> delete from t1 where id = 1 or val = 1
其中 id 和 val 都是索引,那麼執行時使用到了哪些索引,加了哪些鎖呢?爲此,咱們須要使用 explain 來獲取 MySQL 執行這條 SQL 的執行計劃。數據庫
什麼是執行計劃呢?簡單來講,就是 SQL 在數據庫中執行時的表現狀況,一般用於 SQL 性能分析、優化和加鎖分析等場景,執行過程會在 MySQL 查詢過程當中由解析器,預處理器和查詢優化器共同生成。緩存
MySQL 查詢過程
若是能搞清楚 MySQL 是如何優化和執行查詢的,不只對優化查詢必定會有幫助,還能夠經過分析使用到的索引來判斷最終的加鎖場景。服務器
下圖是MySQL執行一個查詢的過程。實際上每一步都比想象中的複雜,尤爲優化器,更復雜也更難理解。本文只給予簡單的介紹。數據結構
MySQL查詢過程以下:工具
- 客戶端發送一條查詢給服務器。
- 服務器先檢查查詢緩存,若是命中了緩存,則馬上返回存儲在緩存中的結果。不然進入下一階段。
- 服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃。
- MySQL根據優化器生成的執行計劃,再調用存儲引擎的API來執行查詢。
- 將結果返回給客戶端。
執行計劃
MySQL會解析查詢,並建立內部數據結構(解析樹),並對其進行各類優化,包括重寫查詢、決定表的讀取順序、選擇合適的索引等。性能
用戶可經過關鍵字提示(hint)優化器,從而影響優化器的決策過程。也能夠經過 explain 瞭解 數據庫是如何進行優化決策的,並提供一個參考基準,便於用戶重構查詢和數據庫表的 schema、修改數據庫配置等,使查詢儘量高效。優化
下面,咱們依次介紹 explain 中相關輸出參數,並以實際例子解釋這些參數的含義。.net
select_type
查詢數據的操做類型,有以下
- simple 簡單查詢,不包含子查詢或 union,以下圖所示,就是最簡單的查詢語句。
-
primary 是 SQL 中包含複雜的子查詢,此時最外層查詢標記爲該值。
-
derived 是 SQL 中 from 子句中包含的子查詢被標記爲該值,MySQL 會遞歸執行這些子查詢,把結果放在臨時表。下圖展現了上述兩種類型。
- subquery 是 SQL 在 select 或者 where 裏包含的子查詢,被標記爲該值。
- dependent subquery:子查詢中的第一個 select,取決於外側的查詢,通常是 in 中的子查詢。
-
union 是 SQL 在出如今 union 關鍵字以後的第二個 select ,被標記爲該值;若 union 包含在 from 的子查詢中,外層select 被標記爲 derived。
-
union result 從 union 表獲取結果的 select。下圖展現了 union 和 union result 的 SQL 案例。
- dependent union 也是 union 關鍵字以後的第二個或者後邊的那個 select 語句,和 dependent subquery 同樣,取決於外面的查詢。
type
表的鏈接類型,其性能由高到低排列爲 system,const,eq_ref,ref,range,index 和 all。
- system 表示表只有一行記錄,至關於系統表。以下圖所示,由於 from 的子查詢派生的表只有一行數據,因此 primary 的錶鏈接類型爲 system。
- const 經過索引一次就找到,只匹配一行數據,用於常數值比較PRIMARY KEY 或者 UNIQUE索引。
-
eq_ref 惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配,經常使用於主鍵或惟一索引掃描。對於每一個來自前邊的表的行組合,從該表中讀取一行。它是除了 const 類型外最好的鏈接類型。
以下圖所示,對錶 t1 查詢的 type 是 ALL,表示全表掃描,而後 t1 中每一行數據都來跟 t2.id 這個主鍵索引進行對比,因此 t2 表的查詢就是 eq_ref。
- ref 非惟一性索引掃描,返回匹配某個單獨值的全部行,和 eq_ref 的區別是索引是非惟一索引,具體案例以下所示。
- range 只檢查給定範圍的行,使用一個索引來選擇行,當使用 =, between, >, <, 和 in 等操做符,並使用常數比較關鍵列時。以下圖所示,其中 id 爲惟一索引,而 val 是非惟一索引。
-
index 與 ALL 類型相似,惟一區別就是隻遍歷索引樹讀取索引值,比 ALL 讀取全部數據行要稍微快一些,由於索引文件一般比數據文件小。這裏涉及 MySQL 的索引覆蓋
-
ALL 全表掃描,一般狀況下性能不好,應該避免。
possible_keys,key 和 key_len
possible_key 列指出 MySQL 可能使用哪一個索引在該表中查找。若是該列爲 NULL,則沒有使用相關索引。須要檢查 where 子句條件來建立合適的索引提升查詢效率。
key 列顯示 MySQL 實際決定使用的索引。若是沒有選擇索引,則值爲 NULL。
key_len 顯示 MySQL 決定使用索引的長度。若是鍵爲 NULL,則本列也爲 NULL,使用的索引長度,在保證精確度的狀況下,越短越好。由於越短,索引文件越小,須要的 I/O次數也越少。
由上圖能夠看出,對於 select * from t2 where id = 1 or val = 1這個語句,可使用 PRIMARY 或者 idx_t2_val 索引,實際使用了 idx_t2_val 索引,索引的長度爲5。
這些實際上是咱們分析加鎖場景最爲關心的字段,後續文章會具體講解如何根據這些字段和其餘工具一塊兒判斷複雜 SQL 到底加了哪些鎖。
ref
ref 列表示使用其餘表的哪一個列或者常數來從表中選擇行。以下圖所示,從 t2 讀取數據時,要判斷 t2.id = t1.id,因此 ref 就是 mysql.t1.id
rows 和 filtered
rows 列顯示 MySQL 認爲它執行查詢時必須檢查的行數。
filtered 列代表了 SQL 語句執行後返回結果的行數佔讀取行數的百分比,值越大越好。MySQL 會使用 Table Filter 來讀取出來的行數據進行過濾,理論上,讀取出來的行等於返回結果的行數時效率最高,過濾的比率越多,效率越低。
如上圖所示,t1表中有三條數據,rows 爲 3,表示全部行都要讀取出來。根據 val = 3 這個 table filter 過濾,只返回一行數據,因此 filtered 比例爲33.33%,
extra
包含不適合在其餘列中顯示但十分重要的額外信息。常見的值以下
-
using index 表示 select 操做使用了覆蓋索引,避免了訪問表的數據行,效率不錯。
-
using where 子句用於限制哪一行。也就是讀取數據後使用了 Table Filter 進行過濾。
以下圖所示,由於 id 和 val 都是有索引的,因此 select * 也是能夠直接使用覆蓋索引讀取數據,因此 extra 中有 using index。而由於只使用 val 索引讀取了3行數據,仍是經過 where 子句進行過濾,filtered爲 55%,因此 extra 中使用了 using where。
- using filesort MySQL 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,若出現該值,應該優化 SQL 語句。以下圖所示,其中 val 列沒有索引,因此沒法使用索引順序排序讀取。
-
using temporary 使用臨時表保存中間結果,好比,MySQL 在對查詢結果排序時使用臨時表,經常使用於 order by 和 group by,若是出現該值,應該優化 SQL。根據個人經驗,group by 一個無索引列,或者ORDER BY 或 GROUP BY 的列不是來自JOIN語句序列的第一個表,就會產生臨時表。
-
using join buffer 使用鏈接緩存。以下圖所示,展現了鏈接緩存和臨時表。關於鏈接緩存的內容,你們能夠自行查閱,後續有時間在寫文章解釋。
- distinct 發現第一個匹配後,中止爲當前的行組合搜索更多的行
後記
經過 explain 瞭解到 SQL 的執行計劃後,咱們不只能夠了解 SQL 執行時使用的索引,判斷加鎖場景,還能夠針對其餘信息對 SQL 進行優化分析,好比將 type 類型從 index 優化到 ref 等。