索引扫描行数远大于实际返回行数,说明索引未被高效利用,常见原因是未使用覆盖索引导致回表、联合索引字段顺序不匹配访问模式、在WHERE中使用函数使索引失效等。

索引扫描行数远大于实际返回行数,说明 MySQL 在索引中“翻了很多页”才找到符合条件的几条记录——这不是数据少导致的快,而是索引没用对、或者用得不充分。
EXPLAIN 的 rows 值大但返回行极少,大概率是没走覆盖索引
MySQL 扫描了大量索引项,却只命中几行,最常见原因是 SELECT 字段不在索引里,被迫回表过滤。比如有索引 idx_status_created(status, created_at),执行 SELECT status, created_at FROM t WHERE status = 'active' 是覆盖查询(Extra 显示 Using index);但换成 SELECT * 或加个 name 字段,就会触发回表,rows 可能从 120 跳到 8500。
- 检查
EXPLAIN的Extra列:出现Using index才算真正覆盖;若只有Using where,说明 WHERE 条件在索引里过滤了,但 SELECT 字段还得回主键查 - 联合索引字段顺序必须匹配访问模式:WHERE 等值字段(如
status)必须在前,范围/排序字段(如created_at)其次,最后才是 SELECT 中的非主键字段 - 大字段(如
TEXT、超长VARCHAR)别塞进索引,宁可回表取主键再查,也别拖慢索引遍历速度
加了 LIMIT 1 反而更慢?优化器可能选错索引路径
优化器看到 LIMIT 1,会倾向选择“看起来最快找到第一条”的路径,比如走一个低区分度的索引(如 status),然后逐行回表判断是否满足其他条件——结果扫了上万行才凑够一条。而没加 LIMIT 时,它反而选了更稳的全索引扫描 + 内存过滤。
- 用
EXPLAIN ANALYZE(MySQL 8.0+)看真实rows和loops:预估的rows=85不可信,实际rows=9800 loops=1才是真相 - 对比不同索引方案时,只改索引、清缓存(
FLUSH STATUS)、跑同一条 SQL,避免统计信息偏差干扰 - 如果
filtered值长期低于 10%,说明该索引选出的数据太水,后续靠WHERE硬过滤,不如换一个高区分度字段打头的联合索引
这些写法会让索引“形同虚设”,哪怕 key 显示非 NULL
key 列显示用了索引,不代表高效。很多日常写法让优化器只能用索引做“定位起点”,后面全靠扫描+过滤。
-
WHERE YEAR(created_at) = 2024→ 改成WHERE created_at >= '2024-01-01' AND created_at -
WHERE user_id = '123'(user_id 是 INT)→ 字符串隐式转整型可能丢索引,统一用123 -
WHERE name LIKE '%abc'→ 左模糊无法走索引;'abc%'可以 -
WHERE a = 1 OR b = 2→ 除非a和b都有独立索引且满足松散索引扫描条件,否则大概率退化为全表扫
真正关键的不是“有没有索引”,而是“索引能不能把 WHERE 过滤、ORDER BY 排序、SELECT 返回这三件事一次性干完”。只要其中一环断了,扫描行数就容易失控——而这个问题,在测试环境往往看不出来,因为数据量小、统计信息准、缓存热。上线后数据一涨、分布一偏,rows 就开始跳着玩了。











