select * 让覆盖索引失效,因其强制回表查询聚簇索引;覆盖索引要求查询字段全部且严格按最左前缀顺序包含在单个索引中,且不含text/blob。

为什么 SELECT * 让覆盖索引失效
覆盖索引生效的前提是:查询所需的所有字段,全部被单个索引“包住”。一旦用了 SELECT *,InnoDB 就不得不回表查聚簇索引——哪怕你只差一个字段没在索引里。
常见错误现象:EXPLAIN 显示 type=ref 但 Extra 列出现 Using where; Using index(说明走了覆盖),而加了 * 后变成 Using where(没了 Using index),性能掉一截。
- 实操建议:用
SELECT明确列出需要的字段,且确保它们全在同一个复合索引中按顺序存在 - 注意字段顺序:索引定义是
(a, b, c),那SELECT a, b可覆盖;但SELECT b, c不行(缺失前导列a) - TEXT/BLOB 类型字段无法被任何索引“覆盖”,哪怕你把它加进索引定义,MySQL 也会忽略——别试
ORDER BY 和覆盖索引冲突时怎么破
覆盖索引能避免排序(Using filesort),但前提是 ORDER BY 字段顺序与索引最左前缀严格一致,且没有混合 ASC/DESC。
使用场景:分页查最新 20 条订单,按 created_at DESC 排序,但业务只要 id 和 status。
- 错误写法:
INDEX (status, created_at)+ORDER BY created_at DESC→ 仍触发Using filesort - 正确写法:建索引
INDEX (status, created_at DESC)(MySQL 8.0+ 支持显式方向声明) - MySQL 5.7 及更早:只能靠
INDEX (status, created_at)+ORDER BY status, created_at DESC组合,否则必回表或排序 - 如果
WHERE条件用了IN或范围(如created_at > '2024-01-01'),ORDER BY很可能失效——此时覆盖索引也救不了排序
压测时发现 key_len 比预期小,是不是索引没用上
key_len 是 MySQL 实际用到的索引字节数,不是定义长度。它变小往往意味着:只用了索引前缀,或字段为 NULL 导致额外字节开销。
常见错误现象:定义了 INDEX (user_id, category_id, tag),但 EXPLAIN 显示 key_len=8(只用了前两个 INT 字段),明明 WHERE 条件三个都写了。
- 检查字段是否允许 NULL:
user_id INT NOT NULL占 4 字节,user_id INT NULL占 5 字节(+1 位空标记)。多几个 NULL 字段,key_len就容易“对不上” - 字符串字段用了前缀索引(如
VARCHAR(255)建了INDEX (name(10))),key_len只算前 10 字符 + 长度字节,不是全长 -
key_len小 ≠ 索引没用上,要结合rows和Extra综合看;但若key_len=0,基本可断定索引完全失效
复合索引里该把高区分度字段放前面还是低区分度的
老说法“高区分度放左”在覆盖查询下不一定最优。真正关键的是:**过滤强度 + 排序需求 + 覆盖完整性**。
性能影响:放错顺序可能导致索引无法用于 WHERE 下推,或无法支持 ORDER BY,最终被迫回表。
- 如果
WHERE总是带tenant_id = ?(低区分度,但必填),且还要查user_id(高区分度),那就得把tenant_id放索引最左——否则没法走索引过滤 - 如果
WHERE条件灵活(有时只有status,有时加created_at),优先保常用组合,而不是死抠区分度数字 - 想支持覆盖,就把 SELECT 字段按使用频率和依赖关系排:比如常查
(status, created_at, id),那就建INDEX (status, created_at, id),哪怕status只有 3 个值
最易被忽略的一点:索引字段顺序一旦定死,就很难靠 Hint 或重写 SQL 绕过。建之前拿真实慢查的 WHERE 和 SELECT 组合跑一遍 EXPLAIN,比凭经验拍更靠谱。











