索引並非萬能,特定查寫模式會導致優化器放棄索引轉為全表掃描。本文總結七大經典失效場景、EXPLAIN 排查方法、避坑最佳實踐。
-- 失效:a 有索引,b 無索引
SELECT * FROM t WHERE a = 1 OR b = 2;
-- 生效:a、b 皆有索引 (或複合索引覆蓋)
SELECT * FROM t WHERE a = 1 OR b = 2;
-- 或改寫為 UNION
SELECT * FROM t WHERE a = 1
UNION
SELECT * FROM t WHERE b = 2;
原理: 優化器評估 OR 成本時,若任一側無索引,傾向全掃描。
-- 索引:INDEX idx_a_b_c (a, b, c)
-- 生效:使用最左列
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
-- 失效:跳過最左列
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3
-- 失效:無法使用索引
WHERE name LIKE '%abc'
WHERE name LIKE '%abc%'
-- 生效:固定前綴
WHERE name LIKE 'abc%'
WHERE name LIKE 'abc_%'
-- 表結構:name VARCHAR(20)
-- 失效:數字常數導致隱式轉換 CAST(name AS UNSIGNED)
WHERE name = 123
-- 生效:字串常數
WHERE name = '123'
現象: EXPLAIN 顯示 type=ALL,Extra 含 Using where 但無 Using index。
-- 場景:查詢返回行數超過表總行數 20-30%
-- 即使有索引,優化器可能選擇全表掃描
WHERE status = 1 -- 假設 90% 行 status=1
對策: 強制索引 FORCE INDEX(idx_status)、調整查詢邏輯、更新統計資訊 ANALYZE TABLE。
-- 失效:索引列被函數包裹
WHERE DATE(created_at) = '2024-01-01'
WHERE YEAR(created_at) = 2024
WHERE name + '' = 'abc'
-- 生效:範圍查詢保持索引列乾淨
WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
-- 表欄位:utf8mb4_unicode_ci
-- 查詢參數:latin1 或 utf8_general_ci
-- 失效:隱式轉換
WHERE name = 'abc' -- 連線字符集非 utf8mb4
-- 生效:顯式指定
WHERE name = _utf8mb4'abc' COLLATE utf8mb4_unicode_ci
-- 或確保連線字符集一致
SET NAMES utf8mb4;
SHOW STATUS LIKE 'Handler_read%';
-- 關鍵指標:
-- Handler_read_key : 越高越好 (索引讀取次數)
-- Handler_read_next : 索引範圍掃描次數
-- Handler_read_rnd : 非索引隨機讀取 (排序/臨時表)
-- Handler_read_rnd_next : 越高越差 (全表掃描次數)
| 欄位 | 理想值 | 失效信號 |
|---|---|---|
| type | ref, eq_ref, range | ALL (全掃), index (全索引掃) |
| possible_keys | 列出候選索引 | NULL (無可用索引) |
| key | 實際使用索引名 | NULL (未使用索引) |
| key_len | 合理長度 | 過長 (未用到全部列) |
| rows | 預估掃描行數少 | 接近總行數 |
| Extra | Using index, Using where | Using temporary, Using filesort |
ANALYZE TABLE 更新統計?long_query_time=1)?