-- 基本語法
EXPLAIN SELECT * FROM users WHERE status = 1;
-- 扩展信息 (MySQL 5.6+)
EXPLAIN EXTENDED SELECT * FROM users WHERE status = 1;
SHOW WARNINGS; -- 查看優化後的查詢重寫
-- 格式化輸出 (MySQL 5.7+)
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE status = 1;
-- 或
EXPLAIN FORMAT=TREE SELECT * FROM users WHERE status = 1; -- 8.0.16+
| 列名 | 含義 | 關鍵值解讀 | 優化指向 |
|---|---|---|---|
| id | 查詢標識符,SELECT 識別符 | 相同 id 同一查詢;越大越先執行;NULL 為結果集合並 | 關注子查詢執行順序 |
| select_type | 查詢類型 | SIMPLE(無子查詢/UNION)、PRIMARY(最外層)、SUBQUERY、DERIVED(派生表/子查詢FROM)、UNION、UNION RESULT | DERIVED/SUBQUERY 易成性能瓶頸 |
| table | 訪問的表(或衍生表 | 實表名、 | 確認訪問順序 |
| partitions | 匹配的分區 (分區表) | p0,p1 / ALL | 分區剪枝驗證 |
| type | 訪問類型(核心指標) | 見下文詳細分級 | **目標:至少 ref,最好 eq_ref/const** |
| possible_keys | 可能用到的索引 | 列出候選索引名,NULL 表示無索引可用 | 檢查索引設計是否覆蓋查詢 |
| key | 實際選擇的索引 | 索引名,NULL 表示全表掃描 | 對比 possible_keys,理解優化器決策 |
| key_len | 使用的索引長度 (字節) | INT=4, BIGINT=8, VARCHAR(N)=3N+2 (utf8mb4),NULL=1 | **越短越好**;檢查聯合索引前綴使用情況 |
| ref | 索引比較的引用 | const(常數)、col_name(列)、func(函數)、NULL | const 最好,func 表示函數導致索引失效 |
| rows | 預估掃描行數 | 基於統計信息估算,**越小越好** | 對比 actual rows (配合 profiling) |
| filtered | 按條件過濾後剩餘行百分比 | 100 = 無過濾;10 = 90% 被過濾 | 低值表明索引選擇性差 |
| Extra | 額外信息(優化關鍵) | 見下文詳細分類 | **重點關注 Using temporary、Using filesort** |
WHERE pk = 1,最快WHERE idx_col = constWHERE idx_col = const OR idx_col IS NULL>, <, >=, <=, IN(), BETWEENORDER BY 列無索引、或索引順序不匹配、或 SELECT 列超出覆蓋索引Distinct:找到第一匹配行後停止(LIMIT 1 效果)Not exists:LEFT JOIN 優化,匹配到即停止Range checked for each record (index map: N):無好索引,每行嘗試選索引,**極慢**Impossible WHERE:WHERE 條件永假,優化器直接返回空Select tables optimized away:聚合函數優化,如 COUNT(*) MyISAM 直接讀統計
-- 原查詢
EXPLAIN SELECT * FROM orders
WHERE user_id = 100
ORDER BY created_at DESC;
-- type: ref, key: idx_user_id, Extra: Using where; Using filesort
-- 優化:創建聯合索引 (where列, order by列)
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
-- 再次 EXPLAIN
-- type: ref, key: idx_user_created, Extra: Using where (filesort 消失!)
-- 原查詢
EXPLAIN SELECT id, name, email FROM users WHERE status = 1;
-- type: ref, key: idx_status, Extra: Using where (需回表查 name, email)
-- 優化:聯合索引包含 SELECT 列
ALTER TABLE users ADD INDEX idx_status_name_email (status, name, email);
-- 再次 EXPLAIN
-- type: ref, key: idx_status_name_email, Extra: Using index (覆蓋索引!)
-- 原查詢
EXPLAIN SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;
-- Extra: Using temporary; Using filesort
-- 優化:利用索引順序
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 或強制使用索引
EXPLAIN SELECT user_id, COUNT(*) FROM orders FORCE INDEX (idx_user_id) GROUP BY user_id;
-- Extra: Using index (無 temporary/filesort)
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE status = 1;
輸出包含:
query_block:查詢塊信息table_access:表訪問詳情(成本估算、索引選擇理由)cost_info:read_cost, eval_cost, prefix_cost, data_read_per_joinused_columns:實際使用列(判斷覆蓋索引)attached_condition:附加條件下推信息
SET SESSION optimizer_trace = 'enabled=on';
SELECT * FROM users WHERE status = 1;
SELECT * FROM information_schema.OPTIMIZER_TRACE;
-- 可見:索引評估成本、範圍優化器決策、JOIN 順序枚舉、條件下推細節
| 誤區 | 真相 |
|---|---|
| EXPLAIN 顯示的 rows 是實際掃描行數 | **預估值**,基於統計信息,可能偏差巨大;結合 FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler_read%'; 看實際 |
| key = NULL 一定要加索引 | 小表 (<1000行) 全表掃描反而更快;大表才需索引 |
| Using index 總是好的 | 覆蓋索引索引過大 (包含長 VARCHAR/TEXT) 反而拖慢;需權衡 |
| EXPLAIN 不執行查詢 | 對於子查詢、衍生表,MySQL 可能**物化執行**部分子查詢以獲取統計 |
EXPLAIN 是 SQL 調優的「聽診器」。核心讀懂三要素:
配合 FORMAT=JSON、optimizer_trace、Handler_read% 多維驗證,從「看懂計劃」進化到「預判優化器決策」,才是調優高手的必經之路。