索引选错表现为EXPLAIN显示使用索引但rows远超预期,主因是低选择性索引、统计信息过期或数据倾斜;应验证过滤比、执行ANALYZE TABLE、检查innodb_stats_persistent和Cardinality值。

EXPLAIN 显示用了索引,但 rows 值远超预期
这是最典型的“索引选错”信号。优化器确实选了某个索引(key 列非 NULL),但预估扫描行数 rows 高得离谱——比如查 100 万行表里某状态,rows 显示 45 万,说明它大概率挑了个低选择性索引(如 status 字段只有 'pending'/'done' 两个值)。
此时别急着删索引,先验证实际过滤比:SELECT COUNT(*) FROM t WHERE status = 'pending';
如果结果占总行数 >15%,那这个索引本就不该承担主力过滤任务;如果只占 0.3% 却仍扫了 45 万行,大概率是统计信息过期或数据倾斜未被采样到。
- 立刻执行
ANALYZE TABLE t;更新统计信息(注意:大表慎用,可能锁表几分钟) - 检查
innodb_stats_persistent是否为ON,否则重启后统计又失效 - 用
SHOW INDEX FROM t;看Cardinality值,低于总行数 1% 就算差(千万级表Cardinality )
慢查询日志里反复出现同一类 WHERE 条件,但执行时间波动极大
比如 WHERE user_id = ? AND status = 'paid' 这条语句,有时 20ms,有时 2s——这往往不是 SQL 本身问题,而是优化器在不同参数下“摇摆”:对某些 user_id 值,它认为走 user_id 索引快;对另一些值(比如超级用户),它又切到 status 索引,结果触发大量回表甚至全表扫描。
这种波动背后常是数据倾斜:某个 user_id 关联 80 万订单,其他用户平均才 3 条。优化器基于平均采样估算代价,自然失准。
- 查
information_schema.STATISTICS确认status是否在复合索引里是第二列(即非最左前缀),如果是,删掉影响小 - 监控状态变量:
Handler_read_rnd_next突增说明大量随机回表,正是低选择性索引拖累的典型表现 - 对极端倾斜键(如已知的超级用户 ID),考虑拆成独立 SQL 路由处理,避开优化器误判
FORCE INDEX 后查询变快,但加了就报错或无效
手动指定索引见效,说明优化器确实选错了;但如果 FORCE INDEX (idx_a) 报错 Unknown index,常见原因是:
- 索引名写错,或大小写不匹配(MySQL 在 Linux 下索引名区分大小写)
- 该索引是前缀索引(如
INDEX idx_name (name(10))),而FORCE INDEX必须写完整定义名,不能简写 - 表用了分区,而索引未在所有分区上生效(
SHOW CREATE TABLE查是否带LOCAL关键字)
更隐蔽的问题是:即使语法正确,FORCE INDEX 也可能被忽略——当 WHERE 条件含函数(如 WHERE UPPER(name) = 'ABC')或隐式类型转换(如 user_id 是 VARCHAR 却传整数)时,索引本身已失效,强制也没用。
ORDER BY 或 GROUP BY 触发 Using filesort / Using temporary,且 EXPLAIN 的 key 不为空
这说明优化器“以为”能用索引排序或分组,但实际做不到。典型场景:
- 索引是 (status, created_at),却 ORDER BY created_at DESC(老版本 MySQL 要求方向完全一致)
- GROUP BY user_id 但索引是 (user_id, amount),而 SELECT 里还用了没覆盖的字段,导致必须回表再聚合
- 复合索引顺序错:比如 WHERE a = 1 ORDER BY b,建了 (b, a) 而不是 (a, b)
关键看 EXPLAIN 的 Extra 列:
- 出现 Using filesort:索引无法满足排序需求,MySQL 得额外排序
- 出现 Using temporary:分组或去重被迫建临时表,I/O 开销飙升
- 重建索引时,等值条件字段必须放最左(如
a = 1→a放第一) - 排序字段紧随其后,且方向需匹配(MySQL 8.0+ 支持混合方向,但 5.7 及更早必须一致)
- 避免在
ORDER BY字段上用函数,ORDER BY DATE(created_at)会直接让索引失效
真正难处理的不是“没索引”,而是“有索引但优化器信错了人”。它依赖的统计信息是抽样估算,数据分布是动态变化的,而你的业务逻辑可能正悄悄制造倾斜。别只盯着 EXPLAIN 的 key 列是否为空——要看 rows 是否合理、Extra 是否藏了陷阱、Handler_read_* 状态是否异常飙升。这些细节比“加个索引”更能暴露真相。


















