Explain
資料庫 EXPLAIN 指令結果值的詳細解釋。
MySQL EXPLAIN
select_type 詳細說明
| 類型 | 說明 |
|---|---|
| SIMPLE | 簡單查詢,不包含子查詢或 UNION。執行效率較高 |
| PRIMARY | 最外層的 SELECT 查詢 |
| UNION | UNION 中第二個或後續的 SELECT 語句 |
| DEPENDENT UNION | UNION 中的 SELECT,其執行依賴於外部查詢結果 |
| UNION RESULT | UNION 查詢的結果集(臨時表) |
| SUBQUERY | 出現在 SELECT 列表、WHERE 或 HAVING 子句中的首個子查詢 |
| DEPENDENT SUBQUERY | 依賴於外部查詢的子查詢,每次外部查詢產生一行都會重新執行 |
| DERIVED | 派生表(FROM 子句中的子查詢),結果會實體化到臨時表 |
| MATERIALIZED | 物化子查詢,MySQL 5.6+ 的優化技術 |
| UNCACHEABLE SUBQUERY | 無法緩存的子查詢(如使用隨機函數、用戶變數) |
| UNCACHEABLE UNION | 包含無法緩存的 UNION 查詢 |
type 詳細說明(效率由高到低)
| 類型 | 說明 |
|---|---|
| system | 表中只有一行記錄(const 的特例) |
| const | 使用主鍵或唯一索引等值查詢,最多匹配一行 |
| eq_ref | 使用主鍵或唯一非空索引連接查詢,對於前表每行組合讀取一行 |
| ref | 非唯一索引掃描,返回匹配某單個值的所有行 |
| fulltext | 使用全文索引執行查詢 |
| ref_or_null | 類似 ref,但另外搜尋包含 NULL 值的行 |
| index_merge | 使用索引合併優化,同時使用多個索引並合併結果 |
| unique_subquery | 用於 IN 子查詢的優化,替代 eq_ref |
| index_subquery | 類似 unique_subquery,但用於非唯一索引 |
| range | 僅檢索指定範圍內的行(=, <>, >, >=, <, <=, BETWEEN, IN 等) |
| index | 全索引掃描,和全表掃描幾乎一樣差 |
| ALL | 全表掃描,效率最低 |
Extra 欄位詳細說明
| 值 | 說明 |
|---|---|
| Using index | 覆蓋索引,只使用索引樹中的信息,大幅提高性能 |
| Using where | 使用 WHERE 子句過濾,在 MySQL 服務器層面過濾 |
| Using temporary | 創建臨時表處理查詢(排序、分組、多表連接) |
| Using filesort | 使用外部排序而非索引順序讀取,潛在性能問題 |
| Using join buffer | 使用連接緩衝提高連接效率 |
| Impossible WHERE | WHERE 子句總是 false,不返回任何行 |
| Select tables optimized away | 聚合函數可從索引直接獲得結果 |
| No tables used | 查詢不涉及表(如 SELECT 1+1) |
| Using index condition | 索引條件下推(ICP),減少檢索行數 |
| Using MRR | 多範圍讀取優化,減少隨機 I/O |
rows 與 filtered 值解讀
rows:MySQL 估計需要檢查的行數,越低越好。在 JOIN 操作中為嵌套循環所需讀取行數的乘積。
filtered:表條件過濾後剩餘的行百分比。例如 filtered=50.00 表示 WHERE 條件將過濾掉約 50% 的行。實際要檢查的行:rows × filtered%。
PostgreSQL EXPLAIN
節點類型詳細解釋
| 類型 | 說明 |
|---|---|
| Seq Scan | 順序掃描,從頭到尾讀取整個表 |
| Index Scan | 索引掃描,先掃描索引再訪問表數據 |
| Index Only Scan | 僅索引掃描,所有數據都在索引中,最高效 |
| Bitmap Scan | 位圖掃描,先創建匹配行的位圖,再按磁盤順序獲取 |
| Nested Loop | 嵌套循環連接,對外表每行掃描內表 |
| Hash Join | 哈希連接,建立哈希表後查找匹配 |
| Merge Join | 合併連接,兩表按連接列排序後並行掃描 |
| Sort | 排序操作 |
| Limit | 限制返回行數 |
| Aggregate | 聚合函數操作(SUM、COUNT 等) |
cost 值的詳細含義
- 數值單位:以任意磁盤頁面獲取為單位
- startup cost:產生第一個輸出行前的初始化成本
- total cost:產生所有輸出行的總成本
cost=0.42..1.42 rows=10 width=14- 0.42:產生第一行的成本
- 1.42:產生所有 10 行的總成本
- 每行平均寬度 14 字節
actual time(使用 EXPLAIN ANALYZE)
actual time=0.015..0.018 rows=10 loops=1- 0.015ms:獲得第一行的時間
- 0.018ms:處理所有行的總時間
- loops:節點執行次數
- 總實際時間 = actual time × loops
緩衝區信息(使用 EXPLAIN (ANALYZE, BUFFERS))
| 指標 | 說明 |
|---|---|
| shared hit | 從共享緩衝區讀取的塊數(數據在內存中) |
| shared read | 從磁盤讀取到共享緩衝區的塊數(物理 I/O) |
| shared dirtied | 被查詢修改的共享緩衝區塊數 |
| shared written | 從共享緩衝區寫入磁盤的塊數 |
| temp read/written | 臨時文件讀寫,可能導致性能問題 |
Filter 與 Index Cond 的區別
- Index Cond:使用索引直接找到匹配行,更高效
- Filter:獲取行後再應用過濾,無法使用索引
MongoDB EXPLAIN
verbosity 模式
| 模式 | 說明 |
|---|---|
| queryPlanner | 默認模式,只顯示查詢計劃不執行 |
| executionStats | 包含執行統計,實際執行查詢 |
| allPlansExecution | 包含所有考慮過的計劃,最詳細 |
stage 詳細說明
| Stage | 說明 | 性能 |
|---|---|---|
| COLLSCAN | 集合掃描,掃描所有文檔 | ❌ 最低 |
| IXSCAN | 索引掃描,通過索引鍵查找 | ✅ 高效 |
| FETCH | 從索引獲取信息後讀取文檔 | 中等 |
| SORT | 內存排序,無法使用索引時出現 | ⚠️ 潛在問題 |
| LIMIT | 限制返回文檔數 | ✅ |
| SKIP | 跳過指定數量文檔 | ⚠️ 大量 SKIP 有性能問題 |
| IDHACK | 使用 _id 字段特殊優化查詢 | ✅ 高效 |
| COUNT_SCAN | 使用索引計數,無需訪問文檔 | ✅ |
| PROJECTION_COVERED | 所有字段都在索引中 | ✅ 最高效 |
executionStats 主要指標
| 指標 | 說明 |
|---|---|
| nReturned | 返回客戶端的文檔數 |
| executionTimeMillis | 執行時間(毫秒) |
| totalKeysExamined | 掃描的索引鍵總數 |
| totalDocsExamined | 掃描的文檔總數(理想應接近 nReturned) |
查詢效率判斷標準
最佳情況:
nReturned ≈ totalKeysExamined ≈ totalDocsExamined- 索引覆蓋查詢:totalKeysExamined > 0 且 totalDocsExamined = 0
- 索引效率:totalKeysExamined / nReturned 比率應接近 1:1
- 文檔掃描效率:totalDocsExamined / nReturned 比率應接近 1:1
危險訊號
- COLLSCAN 在大集合上
- totalDocsExamined » nReturned(掃描了遠多於需要的文檔)
- 高 executionTimeMillis 但低 nReturned
- SORT 階段且 memUsage 接近 memLimit
查詢優化典型案例
COLLSCAN 改進
問題: { stage: "COLLSCAN", totalDocsExamined: 10000, nReturned: 10 }
解決: 為查詢條件創建適當索引索引效率低下
問題: { stage: "IXSCAN", totalKeysExamined: 5000, nReturned: 10 }
解決: 創建更精確的複合索引或調整查詢排序不使用索引
問題: { stage: "SORT", executionTimeMillis: 500, memUsage: 30000000 }
解決: 創建包含排序字段的索引SKIP 效率問題
問題: 使用大量 SKIP 進行分頁
解決: 使用基於上次查詢結果的條件(如 { _id: { $gt: lastId } })