为什么加了索引还慢?MySQL 索引失效 12 个排查点
这篇给你一个能直接拿去线上排查的版本,不讲玄学,只讲怎么定位。
先看结论
SQL 慢、明明建了索引,通常不是“索引没建”,而是:
- 建了但没用上
- 用上了但扫描行太多
- 用上了但回表成本太高
排查顺序就三步:EXPLAIN、看执行条件、改 SQL/索引设计。
一、先用 EXPLAIN 别猜
先跑:
sql
EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 1;重点看这几个字段:
| 字段 | 怎么看 |
|---|---|
type | 至少要到 ref,最好 range / const |
key | 实际命中的索引 |
rows | 预估扫描行数,越小越好 |
Extra | 有没有 Using filesort、Using temporary |
如果 key = NULL,先别急着骂 MySQL,通常是写法把索引“写废了”。
二、最常见的 12 个索引失效点
1) 在索引列上做函数
sql
-- 失效写法
SELECT * FROM user WHERE DATE(create_time) = '2026-03-29';DATE(create_time) 让 B+Tree 没法按原值匹配。
改成范围查询:
sql
SELECT * FROM user
WHERE create_time >= '2026-03-29 00:00:00'
AND create_time < '2026-03-30 00:00:00';2) 隐式类型转换
sql
-- phone 是 varchar,右边给了数字
SELECT * FROM user WHERE phone = 13800138000;字段类型和常量类型不一致,会导致转换,索引可能失效。
统一类型:
sql
SELECT * FROM user WHERE phone = '13800138000';3) 前导模糊匹配
sql
SELECT * FROM product WHERE name LIKE '%手机';以 % 开头无法走普通索引。
可选方案:倒排索引(ES)、前缀搜索重构、全文索引。
4) OR 两边索引条件不对称
sql
SELECT * FROM orders WHERE user_id = 1 OR remark = 'urgent';一边可走索引,一边不走,优化器可能直接全表扫。
可拆成 UNION ALL 两段分别优化。
5) 复合索引没按最左前缀用
有索引 (user_id, status, create_time),你却写:
sql
SELECT * FROM orders WHERE status = 1;没带最左列 user_id,这个复合索引通常用不上。
6) 范围条件后面的列无法继续高效利用
sql
-- 索引:(user_id, create_time, status)
SELECT * FROM orders
WHERE user_id = 1
AND create_time > '2026-03-01'
AND status = 1;命中到范围列后,后续列的过滤收益会明显下降。
常见做法:调整复合索引顺序,把等值高选择度列尽量放前面。
7) != / <> / NOT IN 选择性差
sql
SELECT * FROM user WHERE status <> 1;这类条件经常要扫大范围,优化器可能放弃索引。
8) 回表太重,优化器宁可全表扫
sql
SELECT * FROM orders WHERE user_id = 10086;如果命中行太多,SELECT * 触发大量回表,可能不划算。
尽量改成覆盖索引需要的列:
sql
SELECT id, user_id, status FROM orders WHERE user_id = 10086;9) 排序和索引方向不匹配
sql
SELECT * FROM orders
WHERE user_id = 1
ORDER BY create_time DESC;索引不匹配时会出现 Using filesort。
要么补合适索引,要么减少排序数据集。
10) 分组导致临时表
GROUP BY 字段若没有合适索引,常见 Using temporary。
先过滤再分组,或增加对应索引。
11) 统计信息过旧
数据分布变化后,优化器可能选错执行计划。
可执行:
sql
ANALYZE TABLE orders;12) 小表全扫本来就更快
这个不是失效,是优化器做了正确决策。
几千行的小表,全表扫可能比走索引+回表还快。
三、一个实战排查流程(我平时这么走)
- 抓慢 SQL(慢日志 / APM)
- 跑
EXPLAIN看key/type/rows/Extra - 检查是否存在上面 12 类问题
- 优先改 SQL 写法(函数、类型、范围、
SELECT *) - 再改索引设计(复合索引顺序、覆盖索引)
- 回归压测,对比耗时和扫描行
先改 SQL 往往比“无脑加索引”收益更大。
四、给你一套可复用的优化模板
慢 SQL:
sql
SELECT * FROM orders
WHERE DATE(create_time) = '2026-03-29'
AND status = 1
ORDER BY create_time DESC
LIMIT 20;优化后:
sql
-- 1) 改写时间条件,避免函数
-- 2) 减少返回列,尽量覆盖索引
SELECT id, user_id, status, create_time
FROM orders
WHERE create_time >= '2026-03-29 00:00:00'
AND create_time < '2026-03-30 00:00:00'
AND status = 1
ORDER BY create_time DESC
LIMIT 20;配套索引思路:
sql
-- 按你的查询条件设计复合索引(示例)
CREATE INDEX idx_orders_status_ctime
ON orders(status, create_time);五、最后的建议
排查索引问题时,别问“有没有索引”,要问三件事:
- 这个索引有没有被用到
- 用到后扫描行是不是还很大
- 回表和排序是不是把收益吃掉了
你把这三件事盯住,MySQL 慢查询会少掉一大半。