索引失效的七个常见原因
建了索引查询仍然全表扫描,多数时候不是 MySQL 的 bug,而是查询写法让索引用不上。下面是出现频率最高的七种情况。
1. 对索引列做函数或运算
-- 失效:索引列被包裹在函数里
SELECT * FROM orders WHERE DATE(created_at) = '2026-09-01';
-- 有效:改写成范围
SELECT * FROM orders WHERE created_at >= '2026-09-01' AND created_at < '2026-09-02';
索引存的是原始值,经过函数转换后无法直接定位。运算同理,id + 1 = 10 不走索引,id = 9 走。
2. 隐式类型转换
列是 VARCHAR,条件里却写了数字:WHERE phone = 13800138000。MySQL 会把列值转成数字比较,相当于对每行套了函数。反过来,数字列传字符串通常没事。这条规则不对称,要记住方向。
3. 前导模糊匹配
LIKE '%abc' 无法使用 B+ 树的有序性。LIKE 'abc%' 可以。业务上确实需要后缀匹配时,考虑倒序列 + 前缀匹配的方案。
4. 违反最左前缀
联合索引 (a, b, c) 只有从 a 开始的连续前缀才能使用。WHERE b = 1 用不上,WHERE a = 1 AND c = 3 只能用 a 部分。设计联合索引时把等值条件排前面,范围条件放最后。
5. OR 连接了非索引列
WHERE a = 1 OR b = 2 -- b 没有索引,整句退化为全表
MySQL 需要对每个条件都能定位,否则只能扫全表。可以拆成 UNION,让两段分别走索引。
6. 优化器判断走索引更慢
回表代价高时,优化器可能直接选择全表扫描——这其实是正确行为。二级索引先定位主键再回表,如果预计命中大量行,随机 IO 不划算。可以:
- 用覆盖索引(把需要的列加入索引)避免回表;
EXPLAIN看到Using index就是覆盖索引生效。
7. 统计信息过期
表频繁大批量写入后,索引的基数统计可能失真,导致优化器选错计划。ANALYZE TABLE 可以刷新。
排查套路
EXPLAIN SELECT ...;
-- 关注:type 至少 range、key 是否命中、rows 预估行数
SHOW INDEX FROM t; -- 检查基数 Cardinality 是否符合预期
最后提醒:索引不是越多越好。每个索引都会拖慢写入、占用空间,读多写少的表可以多建,写多读少的表要克制。