面試官:MySQL 執行計劃都有哪些屬性?分別是什么含義?
大家好,我是君哥。
使用 MySQL 時,我們常常通過執行計劃來判斷 SQL 語句的執行效率。那 MySQL 執行計劃有哪些屬性,分別代表什么含義呢?今天來聊一下這個話題。
通過 EXPLAIN 或 EXPLAIN FORMAT=JSON 查看 MySQL 執行計劃時,會看到執行計劃里面有 12 個屬性,這些屬性展示了優化器執行 SQL 的詳細信息,對分析和優化 SQL 性能非常有幫助。如下面的思維導圖。
圖片
為了給屬性增加說明,我們建兩張表,表結構完全一樣:
CREATE TABLE`test1` (
`id`INT(8) NOTNULL AUTO_INCREMENT,
`a`VARCHAR(10) COLLATE utf8_bin DEFAULTNULL,
`b`VARCHAR(10) COLLATE utf8_bin DEFAULTNULL,
`c`VARCHAR(10) COLLATE utf8_bin DEFAULTNULL,
PRIMARY KEY (`id`),
KEY`idx_a` (`a`)
) ENGINE=INNODB AUTO_INCREMENT=5DEFAULTCHARSET=utf8 COLLATE=utf8_bin
CREATETABLE test2 LIKE test1;用存儲過程插入一些數據:
BEGIN
DECLARE i INT;
SET i=1;
WHILE(i<=1000)DO
INSERT INTO test1 VALUES(i, i, i, i);
SET i=i+1;
END WHILE;
INSERT INTO test2 SELECT * FROM test1;執行計劃
id
id 是 SQL 語句執行的順序標識。
1. 如果 id 是相同的,表示子查詢屬于相同層級,這些子查詢會依次被執行,比如 JOIN 語句:
EXPLAIN SELECT t1.*,t2.c AS c FROM test1 t1 JOIN test2 t2 ON t1.id = t2.id WHERE t1.id = 1 ;
圖片
2. 如果 id 不同,表示這些 SELECT 屬于不同層級,id 值越大,執行優先級越高。比如子查詢或嵌套子查詢 id 值通常大于外層查詢的 id。
EXPLAIN SELECT * FROM test1 t1 WHERE EXISTS(SELECT id FROM test2 t2 WHERE t2.id > t1.id);
圖片
3. id 也可能為 NULL,表示聚合查詢,比如 UNION 語句。
EXPLAIN SELECT * FROM test1 t1 UNION SELECT * FROM test2;
圖片
select_type
select_type 表示查詢類型,常見的 select_type 如下:
- SIMPLE: 簡單查詢,不包括子查詢、聚合查詢等。
- PRIMARY: 當 SQL 包含子查詢或者嵌套查詢時,PRIMARY 表示最外層查詢。
- SUBQUERY: 出現在 SELECT 列表或 WHERE 子句中的非相關子查詢(不依賴外層查詢結果)。
EXPLAIN SELECT * FROM test1 t1 WHERE t1.id =(SELECT id FROM test2 t2 WHERE t2.a=100)
圖片
- DEPENDENT SUBQUERY: 出現在 SELECT 列表或 WHERE 子句中的相關子查詢(依賴外層查詢結果)。
執行流程如下:
a. 外部查詢獲取一行數據;
b. 將該行數據相關字段值傳遞給子查詢;
c. 執行子查詢獲取結果;
d. 根據子查詢結果決定是否返回當前行。
這類查詢語句可能有性能問題,尤其是在數據量大的場景下。
EXPLAIN SELECT * FROM test1 t1 WHERE EXISTS(SELECT id FROM test2 t2 WHERE t2.id > t1.id);
圖片
- DERIVED: 派生表,指子查詢產生的臨時表,性能較差。
- MATERIALIZED: 優化器選擇將子查詢結果生成一個臨時表(通常用于 IN 子查詢優化)。
- UNION: UNION 操作中第二個及以后的 SELECT 語句。
EXPLAIN SELECT * FROM test1 t1 WHERE a=1 UNION SELECT * FROM test2 WHERE a=2 UNION SELECT * FROM test2 WHERE a=3;
圖片
- UNION RESULT: UNION 語句的結果集,id 為 NULL,見上面 SQL 執行計劃。
- DEPENDENT UNION: UNION 中的第二個及以后 SELECT 是相關子查詢(依賴外層查詢)。
EXPLAIN SELECT * FROM test1 WHERE id IN (SELECT id FROM test2 WHERE id=3 UNION ALL SELECT id FROM test2 WHERE a = 2)
圖片
- UNCACHEABLE SUBQUERY: 結果集不能被緩存的子查詢,必須重新為外層查詢的每一行進行評估,出現極少。(可能包含非確定性函數如 RAND(), NOW())。
EXPLAIN SELECT * FROM test1 t1 WHERE t1.id =(SELECT id FROM test2 t2 WHERE c=RAND())
table
表示當前查詢正在訪問的是哪個表,可以是正式表,也可以是派生表、物化子查詢或者 UNION 結果。
EXPLAIN SELECT * FROM test1 t1 UNION SELECT * FROM test2;
圖片
partitions
如果查詢涉及分區表,表示查詢將要訪問的分區表。如果值為 NULL,表示未使用分區表。
type
type 是 MySQL 執行計劃中最重要的屬性,表示 SQL 執行所需要的訪問方式,性能從優到差大致排序如下:
- system: 表中只有一行數據,一般是系統表,是 const 的一個特例。
- const: 通過主鍵或唯一索引進行等值查詢,最多只返回一行記錄。優化器將其視為常量,性能非常好。
EXPLAIN SELECT * FROM test1 WHERE id=1
圖片
- eq_ref: 在 JOIN 查詢中,對于來自前表的每一行組合,從本表中使用主鍵或唯一非空索引進行等值匹配查找恰好找到一行記錄,常見于使用主鍵或唯一索引進行等值連接,性能非常好。
EXPLAIN SELECT t1.* FROM test1 t1 LEFT JOIN test2 t2 ON t2.id = t1.id
圖片
- ref: 使用非唯一索引進行等值查詢,可能返回多行匹配記錄,或者在 JOIN 中使用非唯一索引進行部分前綴匹配。
EXPLAIN SELECT t1.*,t2.c AS c FROM test1 t1 JOIN test2 t2 ON t1.a = t2.a;
- fulltext: 使用 FULLTEXT 索引執行查找。通常與 MATCH()、AGAINST() 語句一起使用來進行全文搜索。
ALTER TABLE test1 ADD FULLTEXT (c);
EXPLAIN SELECT * FROM test1 WHERE MATCH(c) AGAINST('keyword');
圖片
- ref_or_null: 跟 ref 相似,不同的是 SQL 條件中包含 NULL 列的數據。
EXPLAIN SELECT * FROM test1 WHERE a='10' OR a = NULL;
- index_merge: 表示優化器使用了索引合并優化(Index Merge Optimization),即對同一個表使用了多個索引進行掃描,然后將掃描結果合并,如果使用了 index_merge,Extra 屬性會出現 Using union/Using sort_union/Using intersect。這個 type 不一定最高效。
EXPLAIN SELECT * FROM test1 WHERE a='900' OR id > 950;
圖片
- unique_subquery: 在某些 IN 子查詢中用來替換 eq_ref,效率更高。
EXPLAIN SELECT * FROM test1 t1 WHERE t1.id IN (SELECT t2.id FROM test2 t2 WHERE t1.a = t2.a) OR t1.a='100';
圖片
- index_subquery: 類似于 unique_subquery,但適用于使用非唯一索引的 IN 子查詢。
- range: 使用索引進行范圍查詢。WHERE 子句中可以包含 BETWEEN, >, <, >=, <=, IN(), LIKE 'prefix%'等范圍查詢操作符。range 類型比全表掃描效率高,但需注意范圍大小。
EXPLAIN SELECT * FROM test1 WHERE a > '1' AND a < '20';
- index: 全索引掃描(Index Scan)。通常發生在使用覆蓋索引或者按照索引順序進行全表掃描。因為只掃描索引樹,不讀取文件,性能比全表掃描(ALL)快。
EXPLAIN SELECT a, b FROM test1 WHERE a > '10';
圖片
- ALL: 全表掃描,性能較差,一種情況是優化器找不到合適的索引,另一種情況是表數據量很小,優化器認為全表掃描更快。
possible_keys
優化器可以考慮使用的索引列表,SQL 語句可能涉及索引都可能被列出。但是優化器最終不一定會選擇哪個索引,如果這個屬性為 NULL,表示沒有可用的索引,需要考慮建索引進行優化。
key
優化器實際決定使用的索引。如果為 NULL,表示優化器決定不使用任何索引,需要全表掃描,一方面可能因為表小,使用全表掃描也很快,另一方面也可能是因為沒有合適的索引,優化器認為全部掃描效率更高。
比如 possible_keys 屬性中有索引,但是優化器認為使用該索引不如全表掃描效率高。
如果屬性值是 PRIMARY 則表示使用了主鍵索引。
key_len
優化器選擇的索引中使用到的字段長度(單位:字節)。
以文章開頭的 SQL 為例,給 a、b 字段加了聯合索引,下面 SQL 的執行計劃看一下:
EXPLAIN SELECT a, b FROM test1;
圖片
那這個 66 是怎么來的呢?
varchr(N)變長字段且允許 NULL,索引長度 = N * 字符集長度參數 + 1(字段可以為 NULL) + 2(變長字段) = 10 * 3 + 1 + 2 = 33,a + b 聯合索引長度就是 66.
字符集長度參數:utf8mb4 為 4 字節,utf8 = 3,gbk = 2,latin1 = 1
這個屬性可以判斷聯合索引被使用了前幾個字段,值越小表示使用的索引部分越少。
ref
顯示 key 屬性指定的索引中,查找值所用到的列或常量(即與索引進行比較的內容)。
常見值說明:
- const:常量值。
EXPLAIN SELECT * FROM test1 WHERE id = '5';
圖片
- 列名,表示使用到另一個表的列,如 JOIN 語句。
EXPLAIN SELECT t1.* FROM test1 t1 LEFT JOIN test2 t2 ON t2.id = t1.id
圖片
- func:表示 SQL 中使用了函數結果。
- NULL:通常出現在 type 屬性是 index(全索引掃描)或 ALL(全表掃描)類型中。
rows
優化器估算的執行該語句需要掃描的行數。只是一個估算值,不是精確值。
rows 基于表統計信息(SHOW TABLE STATUS)和索引統計信息,優化器可能根據這個值選擇執行計劃。
rows 數值越大,意味著需要處理的 I/O 和 CPU 越多,性能越差,因此 rows 屬性值是識別性能瓶頸的重要指標。
filtered
使用 WHERE 子句中的過濾條件后,返回的數據占總數據的百分比,數值范圍 0.00 ~ 100。
filtered 值越低,表示 WHERE 條件過濾效果越好。
Extra
SQL 執行的額外信息,提供了優化器的處理細節。常見的值如下:
- Using index: 使用覆蓋索引,查詢的列完全包含在所使用的索引的列中,無需回表訪問數據行,性能好。
- Using where: 存儲引擎返回數據行后,服務器層需要應用額外的 WHERE 條件進行過濾,如果 rows 很大,則服務器過濾會花不少時間。
- Using temporary: 需要使用臨時表,常見包括 GROUP BY, DISTINCT, UNION, ORDER BY 的 SQL 語句,可以考慮使用索引進行優化,比如讓 ORDER BY 和 GROUP BY 字段走上索引。
- Using filesort: 需要走非索引的排序,如果內存不夠,需要在磁盤進行排序。嘗試 ORDER BY 和 GROUP BY 字段走上索引排序。
- Using index condition: 索引下推,減少回表次數,可以考慮作為性能優化的手段。
- Select tables optimized away: 優化器確定查詢可以只從索引中獲取結果(例如 MIN(key_column), MAX(key_column), COUNT(*) 通過索引覆蓋),甚至不需要訪問表或索引數據(直接使用元數據統計),性能非常好。
- Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access),Using join buffer (hash join): 表示 JOIN 操作使用了 Join Buffer 。
- Impossible WHERE: WHERE 子句的條件始終為 false,比如 WHERE 1=0,不會返回數據。
- Start temporary, End temporary: 用于半連接(semijoin)物化去重策略,可以優化 IN/EXISTS。
- Distinct: 用于 DISTINCT 或 UNIQUE 子查詢,找到第一個后停止繼續查找。
- Range checked for each record: 沒有好的索引可以使用,但是發現在知道前表字段值的情況下后表可能用上某些索引。對于來自前表的每一行,MySQL 都會在后表中通過范圍查詢(range)或索引合并(index_merge)進行數據查詢。性能不太好,但比完全不走索引的 join 語句好一些。
EXPLAIN SELECT * FROM test1 t1 WHERE EXISTS(SELECT id FROM test2 t2 WHERE t2.id > t1.id);
圖片
總結
本文對 MySQL 執行計劃中的屬性進行了詳細介紹,使用執行計劃優化 SQL 時,關注下面幾點:
- 查看 type 避免全表掃描 ALL 和全索引掃描 index,盡量能使用 const, eq_ref, ref, range。
- 查看 key 是否使用了合適的索引。
- 查看 rows 是否過大,結合 filtered 估算最終結果集大小。
- 查看 Extra,避免使用 Using temporary 和 Using filesort,盡量使用 Using index(覆蓋索引)。通過 Using where 過濾結果集,通過 Using index condition 的減少回表次數。
- 通過 key_len 查看聯合索引使用情況。
- 查看 select_type,如果有 DEPENDENT SUBQUERY 或 DERIVED,要考慮進行優化。































