EXPLAIN 是 MySQL 性能調優的「核磁共振」,它揭示優化器如何執行查詢:用什麼索引、怎麼關聯表、掃描多少行、是否需要臨時表或排序。熟練讀懂 EXPLAIN 輸出,是寫出高性能 SQL 的前提。本文逐欄位拆解,並結合實戰案例說明優化思路。
標識 SELECT 的執行順序。id 相同按順序執行;id 不同,值越大越先執行(子查詢)。
EXPLAIN SELECT * FROM (SELECT * FROM t1 LIMIT 10) AS d;
-- 衍生表 id=2 先執行,外層 id=1 後執行
| 類型 | 含義 | 典型場景 |
|---|---|---|
| SIMPLE | 無 UNION/子查詢的簡單查詢 | 單表查詢 |
| PRIMARY | 最外層查詢 | 包含子查詢的 SQL |
| SUBQUERY | SELECT 列表中的子查詢 | SELECT (SELECT 1 FROM t2) FROM t1 |
| DERIVED | FROM 子句中的衍生表 | SELECT * FROM (SELECT * FROM t1) d |
| UNION | UNION 中第二個及後續 SELECT | SELECT 1 UNION SELECT 2 |
| UNION RESULT | UNION 結果合併 | 臨時表合併階段 |
顯示該行數據對應的表名,衍生表顯示為 <derivedN>,UNION 結果為 <unionM,N>。
此欄位最關鍵,直接反映索引使用效率。
| 類型 | 說明 | 性能 | 典型場景 |
|---|---|---|---|
| system | 表僅一行(const 特例) | 極快 | 極少見 |
| const | 主鍵/唯一索引等值查詢,最多一行匹配 | 極快 | WHERE id=1 |
| eq_ref | 唯一索引/主鍵關聯,每張表最多一行匹配 | 極快 | JOIN ON pk=fk |
| ref | 非唯一索引等值查詢/關聯,可能多行匹配 | 快 | WHERE status=1 |
| ref_or_null | ref + NULL 值查詢 | 快 | WHERE col=1 OR col IS NULL |
| index_merge | 多索引合併(intersection/union/sort-union) | 中等 | WHERE a=1 OR b=2 |
| range | 索引範圍掃描 | 中等 | BETWEEN, IN, >, <, LIKE 'prefix%' |
| index | 全索引掃描(不讀數據文件) | 較慢 | SELECT idx_col FROM t |
| ALL | 全表掃描 | 最差 | 無索引或條件不命中索引 |
優化目標: 至少達到 range,最好是 ref/eq_ref/const。
possible_keys:理論上可用的索引key:實際選用的索引(NULL 表示無索引)key_len:索引使用的字節數,推斷聯合索引用了幾列顯示哪個欄位或常數與 key 配合查找。常見值:const(常數)、db.tbl.col(關聯欄位)、func(函數結果)。
基於統計信息估算,值越大越差。乘積 反映整體查詢代價。
| 值 | 含義 | 優化方向 |
|---|---|---|
| Using index | 覆蓋索引,僅讀索引樹 | 優秀,無需回表 |
| Using where | Server 層過濾(存儲引擎返回後再篩選) | 正常,但若 type=ALL 則需加索引 |
| Using index condition | ICP(Index Condition Pushdown),存儲引擎層過濾 | 優於 Using where |
| Using filesort | 需額外排序(無索引支持 ORDER BY) | 加索引消除排序 |
| Using temporary | 需建臨時表(GROUP BY/DISTINCT/ORDER BY 不同表) | 優化索引/調整 SQL 結構 |
| Using join buffer | 使用 Join Buffer(無索引關聯) | 加關聯索引 |
| Impossible WHERE | WHERE 條件永假 | 檢查邏輯錯誤 |
-- 原查詢:驅動表 city,排序非驅動表 ads.id → 產生臨時表+文件排序
EXPLAIN SELECT ads.id FROM ads, city
WHERE city.city_id = 8005 AND ads.status = 'online' AND city.ads_id = ads.id
ORDER BY ads.id DESC;
-- 優化:排序驅動表 city.ads_id(等價於 ads.id) → 無臨時表
EXPLAIN SELECT ads.id FROM ads, city
WHERE city.city_id = 8005 AND ads.status = 'online' AND city.ads_id = ads.id
ORDER BY city.ads_id DESC;
原理:MySQL 採用 Nested Loop Join,驅動表結果集直接有序則無需臨時表;非驅動表排序需將關聯結果存入臨時表再排序。永遠用小結果集驅動大結果集,且 ORDER BY 跟隨驅動表索引順序。
count(1)/count(pk) 優於 count(*)?錯。MySQL 對 count(*) 有專門優化。count(col) 等同 count(*)?錯。前者統計非 NULL 行數,後者統計總行數。select a,b 比 select a,b,c 讀取更少數據?多數情況錯。行式存儲下整行讀取,除非覆蓋索引避免回表。ORDER BY 一定排序?錯。若索引順序滿足排序需求,優化器會省略排序。Using filesort 表示磁盤排序?錯。只要有排序操作就顯示,實際可能在內存完成。JOIN(MySQL 優化器統計信息有限,複雜 JOIN 易誤判)SELECT *(影響覆蓋索引、增加排序/網絡開銷)JOIN 代替子查詢(MySQL 子查詢執行計劃長期存在缺陷)OR,改用 UNION ALL(優化器對 OR 處理較弱)UNION ALL 代替 UNION(避免唯一性過濾排序)讀懂 EXPLAIN,核心在於關注 type(訪問策略)、key(實際索引)、rows(掃描規模)、Extra(額外開銷)。結合「減少 IO、降低 CPU」兩大目標,針對性調整索引與 SQL 結構,方能寫出經得起高併發考驗的查詢語句。