一、最常見:對索引列做運算 / 函數
? 1. 對索引列使用函數
SELECT * FROM user WHERE YEAR(create_time) = 2024;
原因:
數據庫需要對每一行先算 YEAR(),無法走 B+Tree 有序結構。
? 正確寫法:
SELECT * FROM user
WHERE create_time >= '2024-01-01'
AND create_time < '2025-01-01';
? 2. 對索引列做計算
SELECT * FROM user WHERE age + 1 = 20;
等價于:
age = 19
但因為對列做了運算,索引失效。
二、隱式類型轉換
? 字符串列用數字查詢
-- phone 是 varchar
SELECT * FROM user WHERE phone = 13800000000;
數據庫會對 phone 做隱式轉換,索引失效。
? 應寫成:
WHERE phone = '13800000000'
三、LIKE 使用不當
? 左模糊查詢
WHERE name LIKE '%張'
原理:
B+Tree 是按前綴排序的,前面不確定,無法定位范圍。
? 可以使用索引
WHERE name LIKE '張%'
因為是前綴匹配。
四、OR 使用不當
WHERE name = '張三' OR age = 18
如果:
- name 有索引
- age 沒索引
?? 可能全表掃描
解決方法:
- 兩邊都加索引
- 或改成 UNION
五、違反最左前綴原則(聯合索引)
假設有聯合索引:
index(a, b, c)
? 跳過最左列
WHERE b = 10
索引失效。
? 中間斷裂
WHERE a = 1 AND c = 3
只能用到 a,c 用不上。
?? 遇到范圍查詢會停止匹配
WHERE a = 1 AND b > 2 AND c = 3
用到:
- a
- b
但 c 用不到。
因為范圍查詢會截斷索引匹配。
六、!= 或 <> 不等于
WHERE age != 20
MySQL 可能放棄索引,因為選擇性太差。
七、IS NULL / IS NOT NULL
- IS NULL 通??梢宰咚饕?/li>
- IS NOT NULL 可能失效(取決于數據分布)
八、數據量太少 / 優(yōu)化器選擇放棄
即使有索引,也可能不用。
例如:
WHERE gender = '男'
如果 90% 都是男:
?? 選擇性太差
?? 優(yōu)化器直接全表掃描更快
九、索引列在表達式右邊
WHERE 10 = age
通常還能用索引,但某些版本優(yōu)化不好。
十、索引列參與字符串拼接
WHERE CONCAT(first_name, last_name) = '張三'
必定失效。
十一、使用 NOT IN
WHERE id NOT IN (1,2,3)
可能不走索引。
十二、覆蓋索引 vs 回表
有時候你以為索引失效,其實:
- 索引用了
- 但需要回表
- 性能依然慢
十三、統(tǒng)計信息過期
MySQL 依賴統(tǒng)計信息決定是否使用索引。
統(tǒng)計失真:
ANALYZE TABLE user;
總結一張腦圖邏輯
索引失效本質原因只有三類:
1?? 破壞了“有序性”
- 函數
- 計算
- 左模糊
2?? 優(yōu)化器覺得不劃算
- 選擇性太低
- 數據量小
3?? 違反 B+Tree 使用規(guī)則
- 最左前綴
- 范圍截斷
給你一個實戰(zhàn)建議
判斷是否失效,不要猜:
EXPLAIN SELECT ...
看:
- type 是否為 ALL(全表掃描)
- key 是否為 NULL
- rows 掃描行數