MySQL 索引失效的六个真实场景
“我明明加了索引,为什么还是全表扫描?”
这个问题几乎每个月都会遇到一次。索引失效不是数据库的 bug,绝大多数情况下是查询写法让优化器判断”用索引反而更慢”。
下面六个场景都来自真实项目,附 EXPLAIN 对照。
一、对索引列做了函数运算
-- 失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-10-01';
-- 有效
SELECT * FROM orders
WHERE created_at >= '2026-10-01 00:00:00'
AND created_at < '2026-10-02 00:00:00';
DATE(created_at) 让索引列变成了一个表达式,B+ 树是有序的,但函数值的顺序和原值顺序不一致,只能逐行计算。
同理还有 SUBSTRING()、CONCAT()、YEAR()。
二、隐式类型转换
字段是 varchar,查询传了数字:
-- phone 是 varchar,这里会失效
SELECT * FROM users WHERE phone = 13800138000;
MySQL 会把字符串列转换成数字再比较(不是反过来),等价于对每一行执行 CAST(phone AS DOUBLE),索引直接废掉。
这类问题在 Java 项目里特别常见——Long 类型的参数直接拼进了 SQL。
三、模糊查询以通配符开头
SELECT * FROM articles WHERE title LIKE '%架构%'; -- 全表扫描
SELECT * FROM articles WHERE title LIKE '架构%'; -- 走索引
前缀不确定,B+ 树无法定位起点。真的要做全文模糊搜索,用全文索引或者 ES,别硬扛。
四、联合索引不符合最左前缀
建了 (a, b, c) 的联合索引:
WHERE a = 1 AND b = 2 AND c = 3 -- 全用上
WHERE a = 1 AND c = 3 -- 只用到 a
WHERE b = 2 AND c = 3 -- 完全用不上
顺序不是必须和定义一致(优化器会调整),但跳过的列之后的索引部分就断了。
有个例外:a = 1 AND c = 3 在 MySQL 8 里能用上 ICP(索引条件下推),但 c 不是用来定位范围,只是回表前过滤。
五、OR 连接了没有索引的列
SELECT * FROM users WHERE name = 'leo' OR age = 30;
只要 age 没索引,整个查询就退化成全表扫描。改成 UNION:
SELECT * FROM users WHERE name = 'leo'
UNION
SELECT * FROM users WHERE age = 30;
六、区分度太低
性别、状态、是否删除这类字段,全表只有几个值。当某个值占比超过 20%~30%,优化器会认为”走索引再回表”比”顺序扫全表”更贵,直接放弃索引。
-- status 只有 0/1,且 95% 是 1
SELECT * FROM tasks WHERE status = 1;
这种情况不该加索引,该考虑的是改变查询模型——比如把”待处理”单独拆表,或者用覆盖索引把回表开销消掉。
怎么确认
不要猜,看执行计划:
EXPLAIN SELECT ...;
-- 关注三列
-- type: ALL 表示全表扫描,range/ref 才是走索引
-- key: 实际使用的索引,NULL 就是没用
-- rows: 预估扫描行数
MySQL 8 还有 EXPLAIN ANALYZE,会真正执行并给出实际耗时,比 EXPLAIN 的预估值准得多。
小结
索引失效的本质只有一句话:优化器认为不用索引更快。
所以排查时不要问”为什么没用索引”,要问”它为什么判断不用更快”——是函数让索引列失去有序性,是类型转换引入了逐行计算,还是回表成本超过了顺序扫描。