在日常开发与面试中,经常会听到一句话:不要用 SELECT *,会导致索引失效。这句话流传极广,但也极度片面,甚至误导了很多开发者。
今天我们就以 SELECT * 是否必然导致索引失效 为切入点,系统梳理 MySQL 索引失效的常见场景、底层原因、优化方案,帮你真正从原理层面理解索引,而不是死记八股文。
很多人一看到 SELECT * 就默认索引废了,这是典型的只知表象,不知原理。
SELECT * ≠ 索引失效只要 WHERE 条件字段命中索引,MySQL 优化器依然会优先考虑走索引。SELECT * 属于非覆盖索引查询,查到索引后必须回聚簇索引取整行数据。虽然 SELECT * 不直接失效,但生产环境严禁无脑使用。 正确做法:
EXPLAIN 的 Extra 出现:Using index 彻底避免回表,性能提升一个量级。理解了 SELECT * 的本质,我们再来看真正会导致索引失效的场景,这也是面试与开发中最常踩的坑。
底层原理联合索引是按照字段从左到右依次排序的,跳过最左列,或在中间使用范围查询(> < between),会直接导致后面的列无法使用索引。
注意:MySQL 8.0.13 支持索引跳跃扫描(ISS),可以在缺失最左列时尝试走索引,但不应该依赖它,且 8.0.31 存在 Bug 可能丢数据。
失效示例索引:(sname, s_code, address)
-- 跳过最左列 sname
SELECT * FROM students WHERE s_code = 1;
-- 范围查询后,address 不再使用索引
WHERE sname='A' AND s_code>1 AND address='Shanghai';
优化方案
底层原理索引 B+Tree 存储的是字段原始值,是有序的。 一旦对索引列做计算、函数处理,值的有序性被破坏,MySQL 无法使用二分查找,只能全表扫描。
失效示例
-- 索引列上运算
WHERE height + 1 = 170;
-- 索引列使用函数
WHERE DATE(create_time) = '2022-01-01';
优化方案把计算挪到等号右边,用范围查询代替函数:
WHERE create_time
BETWEEN '2022-01-01 00:00:00'
AND '2022-01-01 23:59:59';
底层原理B+Tree 按字符从左到右排序,以 % 开头,MySQL 不知道从哪里开始匹配,只能全表扫描。
失效示例
WHERE sname LIKE '%Guide';
WHERE sname LIKE '%Guide%';
优化方案
LIKE 'Guide%'底层原理
失效示例
WHERE sname = '学生1' OR address = '上海';
优化方案拆分为 UNION ALL / UNION,让每一段独立走索引。
两个典型问题:
优化建议
NOT EXISTS 或 LEFT JOIN + IS NULL 替代 NOT IN黄金规则
转换发生在索引列上 → 索引失效 转换发生在常量上 → 索引正常
字符串与数字比较时,MySQL 会把字符串转成数字。
场景 | SQL | 转换方向 | 索引是否有效 |
|---|---|---|---|
varchar = 数字 | varchar_col = 123 | 索引列转数字 | ❌ 失效 |
int = 字符串 | int_col = '123' | 常量转数字 | ✅ 有效 |
开发中 80% 的“莫名其妙索引失效”,都来自这里。
严格说不是索引失效,但会产生极慢的文件排序。
出现 Using filesort 常见原因:
优化方案让同一个联合索引同时满足:
EXPLAIN 执行计划 重点看:type:索引访问效率Extra:是否 Using index、Using filesort、Using temporarySELECT * 会不会导致索引失效?
真正决定索引是否失效的,从来不是 SELECT *,而是:你是否理解 B+Tree、是否遵守索引规则、是否控制了回表成本。
下次再有人跟你说“用 SELECT * 索引就废了”,你可以把这篇文章发给他。