select 会让联合索引失效,因为覆盖索引要求 select、where 等所有涉及字段均包含在索引中,而 必然引入未索引列,导致回表查询。

为什么 SELECT * 会让联合索引失效
MySQL 在执行 JOIN 时,如果 SELECT * 涉及的字段超出了联合索引能覆盖的范围,优化器会放弃使用该索引的“覆盖扫描”能力,转而回表查主键——哪怕 WHERE 条件本身完全命中索引。根本原因是:覆盖索引要求查询所需的所有列(包括 SELECT、WHERE、ORDER BY、GROUP BY 中涉及的列)都包含在同一个索引中。
常见错误现象:EXPLAIN 显示 type=ref 或 range,但 Extra 列出现 Using where; Using index(说明用了覆盖),一旦加一个没建在索引里的 SELECT 字段,立刻变成 Using where(没了“Using index”),rows 显著上升。
- 联合索引
(a, b, c)可以覆盖SELECT a, b WHERE a = ? AND b > ? - 但
SELECT a, b, d FROM t WHERE a = ?即使d在另一张 JOIN 表里,也可能导致驱动表的索引无法覆盖(尤其STRAIGHT_JOIN或JOIN顺序固定时) -
TEXT/BLOB类型字段无法被任何索引覆盖,只要SELECT包含它们,必然回表
如何确认当前查询是否走覆盖索引
直接看 EXPLAIN FORMAT=TREE(MySQL 8.0+)或传统 EXPLAIN 的 Extra 字段。重点不是有没有用到索引,而是有没有 Using index。
实操建议:
- 对目标查询执行
EXPLAIN SELECT ...,检查每一行的Extra是否含Using index - 若有多表
JOIN,逐个看每张表对应的EXPLAIN行,注意table列和key列是否匹配预期索引 - 用
SHOW INDEX FROM table_name核对索引列顺序和包含字段,注意:联合索引最左前缀生效,但覆盖要求“全字段命中” - 临时删掉
SELECT中疑似“多余”的字段(比如created_at、content),再EXPLAIN对比Extra变化
优化 Select 字段范围的具体操作
不是简单删字段,而是按数据流向分层控制:驱动表字段尽量精简 + 被驱动表只取必要列 + 避免隐式转换干扰索引选择。
- 明确驱动表:用
STRAIGHT_JOIN固定连接顺序后,优先保证驱动表的SELECT字段全部落在其联合索引中 - 被驱动表字段不要用
*:例如JOIN users u ON t.uid = u.id,只写u.name, u.status,而不是u.*;再根据实际需要为(id, name, status)建联合索引 - 避免在
SELECT中做计算或函数调用:如SELECT UPPER(name)会让name索引失效(即使它在索引里),改用应用层处理 - 留意字符集/排序规则不一致:比如关联字段一边是
utf8mb4_0900_as_cs,一边是utf8mb4_general_ci,会导致无法使用索引覆盖,EXPLAIN中key为空
联合索引设计要匹配 SELECT + WHERE + JOIN 条件
只为 WHERE 建索引不够,必须把 SELECT 里用到的列也塞进去,且顺序有讲究:等值条件列放最左,范围查询列居中,SELECT 非等值字段放最右。
举例:查询语句为 SELECT t.id, t.title, u.nickname FROM posts t JOIN users u ON t.uid = u.id WHERE t.status = 1 AND t.created_at > '2024-01-01' ORDER BY t.id DESC,那么对 posts 表,理想索引是 (status, created_at, id, title) —— status 是等值,created_at 是范围,id 和 title 是 SELECT 所需且用于排序的字段。
容易被忽略的一点:如果 JOIN 条件字段(如 t.uid)不在这个索引里,MySQL 仍可能拒绝覆盖,因为需要拿 uid 去关联合并;此时要么把 uid 加进索引((status, created_at, uid, id, title)),要么确保 users 表有高效索引支持 uid 查找(如主键或唯一索引)。











