Skip to content

为什么加了索引还慢?MySQL 索引失效 12 个排查点

这篇给你一个能直接拿去线上排查的版本,不讲玄学,只讲怎么定位。

先看结论

SQL 慢、明明建了索引,通常不是“索引没建”,而是:

  1. 建了但没用上
  2. 用上了但扫描行太多
  3. 用上了但回表成本太高

排查顺序就三步:EXPLAIN、看执行条件、改 SQL/索引设计。


一、先用 EXPLAIN 别猜

先跑:

sql
EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 1;

重点看这几个字段:

字段怎么看
type至少要到 ref,最好 range / const
key实际命中的索引
rows预估扫描行数,越小越好
Extra有没有 Using filesortUsing 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) 小表全扫本来就更快

这个不是失效,是优化器做了正确决策。
几千行的小表,全表扫可能比走索引+回表还快。


三、一个实战排查流程(我平时这么走)

  1. 抓慢 SQL(慢日志 / APM)
  2. EXPLAINkey/type/rows/Extra
  3. 检查是否存在上面 12 类问题
  4. 优先改 SQL 写法(函数、类型、范围、SELECT *
  5. 再改索引设计(复合索引顺序、覆盖索引)
  6. 回归压测,对比耗时和扫描行

先改 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);

五、最后的建议

排查索引问题时,别问“有没有索引”,要问三件事:

  1. 这个索引有没有被用到
  2. 用到后扫描行是不是还很大
  3. 回表和排序是不是把收益吃掉了

你把这三件事盯住,MySQL 慢查询会少掉一大半。