EXPLAIN 输出里 key 字段为空,是不是没走索引? 先别急着下结论。看到 key 列显示为 NULL,很多人的第一反应是“索引没生效”。其实,这仅仅表示 MySQL 优化器在最终执行时,没有选择使用任何索引来扫描数据。背后的原因,可能比你想象的要复杂一些。 通常,这扇“索引之门”没被推开,不
先别急着下结论。看到 key 列显示为 NULL,很多人的第一反应是“索引没生效”。其实,这仅仅表示 MySQL 优化器在最终执行时,没有选择使用任何索引来扫描数据。背后的原因,可能比你想象的要复杂一些。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
通常,这扇“索引之门”没被推开,不外乎几种情况:索引本身可能已经失效;表的统计信息太久没更新,误导了优化器;查询条件没能匹配上索引的“最左前缀”原则;或者,干脆是因为数据量太小,优化器觉得全表扫描反而更快。
遇到这种情况,该怎么排查呢?可以按这个顺序来:
SHOW INDEX FROM table_name 命令,确认索引是不是真的建好了,字段顺序是否符合你的查询习惯。(a, b, c) 的联合索引,查询却只用 b = ,那索引大概率是用不上的。ANALYZE TABLE table_name,更新表的统计信息。这在经历大批量数据增删后尤其重要。FORCE INDEX 语法做个临时验证,比如 SELECT * FROM t FORCE INDEX (idx_a_b) WHERE a = 1,看看强制走索引后效果如何。如果说 key 字段告诉你“用了哪个索引”,那么 ref 字段就是在解释“拿什么值去索引里找”。它清晰地揭示了查询条件与索引列之间的绑定关系,是判断索引是否被高效利用的关键线索。
这几个常见的 ref 值,含义大不相同:
const:这是最理想的情况。表示 WHERE 条件里用了常量值进行等值匹配,比如 id = 123 或者主键查询,效率通常最高。db.t.a):这通常出现在 JOIN 查询中。意思是,当前表的索引查找,是靠另一张表或子查询里的某个列值来驱动的。func:这是一个需要警惕的信号。它说明查询条件里用了函数或表达式,比如 WHERE YEAR(created_at) = 2023。在大多数情况下(除非你用了 MySQL 8.0.13 以上版本并创建了函数索引),这会导致索引失效。NULL:表示没有用到索引的等值查找逻辑。这可能是因为查询走了范围扫描(type 是 range),或者干脆就是全表扫描(type 是 ALL)。虽然都带“扫描”二字,但 type = index 和 type = ALL 的性能差别,可以说是天壤之别。
type = index 是**索引全扫描**。它遍历的是索引 B+ 树的叶子节点。由于索引通常比完整的数据行小得多,且存储在连续的页中,所以这种扫描的 I/O 开销小,速度更快,有时还能利用索引的有序性避免额外的排序。
而 type = ALL 是**聚簇索引(也就是数据页)全扫描**。它需要把整张表的每一行数据都读出来,开销巨大,是性能优化中要尽力避免的情况。
那么,什么情况下会触发效率相对较高的 index 扫描呢?
SELECT a,b FROM t WHERE a > 10,而 (a,b) 正好是一个联合索引,这样数据库只需读索引,无需回表。Using filesort。type 显示为 index,性能也可能非常糟糕。这可能是最让人困惑的场景之一:明明 key 字段有值,说明索引用上了,可预估的扫描行数(rows)却大得惊人。这其实传递了一个明确信号:索引虽然被用了,但用得很不高效。
rows 这个值是优化器基于统计信息预估的。它偏高,通常指向两个问题:要么是索引的选择性太差(即该列重复值太多),要么是查询条件只命中了索引中效率很低的前缀部分。
排查时,可以顺着这几个方向思考:
SELECT COUNT(DISTINCT col) / COUNT(*) FROM t。如果结果低于 0.1(即10%),那么这个索引的价值可能就很有限了。LIKE '%xxx' 这种前导通配符,或者用 OR 连接了非索引列。这些写法都会导致索引只能部分生效,甚至退化为低效的范围扫描。EXPLAIN FORMAT=JSON 输出里的 used_range_access_method 和 range_details 字段,看看优化器实际划定的扫描范围是不是过大。所以说,读懂 EXPLAIN,难点从来不是看 key 是否为空。真正的功夫,在于理解 ref 背后的查找逻辑,分析 rows 为何虚高,以及洞察 type 背后隐藏的磁盘 I/O 代价。这些细节,如果不翻看执行计划的 JSON 格式输出,光盯着传统表格的那几列,是很容易误判的。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述