索引“走了”不代表“走得好”,key非NULL仅说明选用了索引,真正影响性能的是rows值——若远超实际返回行数,表明索引选择性差、统计信息过期或最左前缀未匹配;Extra出现Using filesort/Using temporary、隐式转换或函数包裹字段也会导致索引低效。
EXPLAIN里key不为空,但rows值远超实际结果行数
索引“走了”,不代表“走得好”。explain中key列有值,只说明优化器选了某个索引;真正影响速度的是rows列——它代表mysql预估要扫描的行数。如果rows=82000但最终只返回12行,说明索引选择性差,或者统计信息过期,导致大量无效扫描。
实操建议:
- 运行
ANALYZE TABLE table_name;更新表统计信息,让优化器重新评估索引效率 - 检查该索引字段的区分度:比如
status只有'active'/'inactive'两个值,即使加了索引,MySQL也可能弃用它(因为全表扫描成本更低) - 若复合索引是
(a, b, c),但查询只用了WHERE b = ?,则rows可能极大——最左前缀没命中,索引实际失效
Extra显示Using filesort或Using temporary
哪怕type是ref、key非空,只要Extra里出现Using filesort或Using temporary,就说明排序或分组没走索引,MySQL被迫在内存或磁盘上额外做操作。
常见场景:
-
ORDER BY created_at DESC,但created_at不在WHERE用到的索引里,也没单独建索引 -
GROUP BY user_id配合SELECT COUNT(*),但user_id索引未覆盖查询所需字段,触发临时表 - 联合索引
(status, created_at),查询WHERE status = 'paid' ORDER BY created_at能走索引;但换成ORDER BY id就必然Using filesort
Navicat默认拉取全部字段,含大文本或BLOB
看起来是“查询慢”,实际卡在传输和渲染。Navicat执行SELECT *时,若某列是TEXT或BLOB,平均长度2MB,哪怕只查10行,也要传20MB数据——网络+客户端解析都拖慢体验,跟索引无关。
验证方式:
- 把
SELECT *改成只选几个小字段(如id, name, status),再测响应时间 - 右键结果集 → “选项” → 取消勾选“显示 BLOB 内容”,看是否明显变快
- 在查询前加
SELECT ROW_COUNT(), @@session.max_allowed_packet;,确认不是因包大小限制触发重试或截断
索引字段被隐式转换或函数包裹
EXPLAIN显示key有值,但实际没生效——典型症状是possible_keys和key一致,rows却异常高。往往是因为WHERE条件表面用了索引字段,但底层发生了类型转换或函数计算。
典型错误写法:
-
WHERE mobile = '13812345678'(mobile是BIGINT)→ 触发隐式转换,索引失效 -
WHERE DATE(created_at) = '2023-01-01'→ 函数包裹,索引完全跳过 -
WHERE status IN ('1', '2')(status是TINYINT)→ 字符串数组引发类型转换
这类问题不会报错,EXPLAIN也“假装走了索引”,但执行时仍全表扫。必须手动核对字段类型与条件字面量是否严格一致。
真正卡住的地方,常常藏在EXPLAIN看似“正常”的细节里:rows虚高、Extra悄悄加料、字段类型肉眼难辨的错配——这些比“没索引”更难察觉,也更容易被忽略。











