優化器根據統計資訊估算各執行計畫成本,選擇最低成本路徑。當統計資訊不準確/缺失時,優化器可能做出次優決策。
EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.status = 1;
關鍵輸出欄位解讀:
| 欄位 | 關鍵值 | 含義 |
|---|---|---|
type | system/const/eq_ref/ref/range/index/ALL | 存取類型:const/eq_ref 最好,ALL 最差 (全表掃描) |
possible_keys | 索引名列表 | 可能使用的索引 |
key | 實際使用索引 | 實際選擇的索引 (NULL 表示無索引) |
key_len | 位元組數 | 索引鍵長度,推斷複合索引使用了幾個欄位 |
rows | 預估掃描行數 | 越小越好,大表應 < 1% 總行數 |
Extra | Using index/Using where/Using temporary/Using filesort | Using index 覆蓋索引最佳;Using filesort 需排序、效能隱憂 |
ANALYZE TABLE t1, t2;
-- 或全部資料庫
ANALYZE TABLE db1.table1, db1.table2, db2.table3;
cardinality (基數統計)
-- 強制使用指定索引
SELECT * FROM t1 FORCE INDEX (idx_status) WHERE status = 1;
-- 建議使用 (優化器可選擇其他)
SELECT * FROM t1 USE INDEX (idx_status) WHERE status = 1;
-- 忽略指定索引
SELECT * FROM t1 IGNORE INDEX (idx_bad) WHERE status = 1;
⚠️ 慎用 FORCE INDEX:資料分布變化可能導致強制索引反而變慢,建議配合監控定期評估。
| 變數 | 預設 | 調優方向 | 說明 |
|---|---|---|---|
max_seeks_for_key | 18446744073709551615 | 設為較小值 (如 1000) | 假設索引查找不超過 N 次,促使優化器傾向索引而非全表掃描 |
optimizer_switch | 多項開關 | 微調特定優化規則 | index_merge=on,mrr=on,batched_key_access=off... |
optimizer_search_depth | 62 | 複雜查詢可適當減小 | 限制優化器搜尋深度,避免過度優化耗時 |
optimizer_trace | OFF | ON (除錯用) | 輸出優化器決策過程 JSON,極其詳細 |
-- 原始慢查詢
SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;
-- EXPLAIN 顯示:type=ALL, rows=5000000, Extra=Using where; Using filesort
-- 方案一:建立複合索引 (WHERE + ORDER BY)
CREATE INDEX idx_user_status_created ON orders (user_id, status, created_at DESC);
-- 方案二:若 user_id 選擇性極高,單欄索引配合 LIMIT 下推 (MySQL 5.6+)
CREATE INDEX idx_user_id ON orders (user_id);
-- 方案三:覆蓋索引 (僅查詢部分欄位)
CREATE INDEX idx_cover ON orders (user_id, status, created_at DESC, order_id, amount);
SELECT order_id, amount FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;
-- Extra: Using index (無需回表)
FORCE INDEX:資料分布變化導致效能倒退ANALYZE TABLEpt-query-digest 分析慢查詢日誌optimizer_trace 基準對比