select *几乎永远无法命中覆盖索引,因其要求查询所有字段(含text/blob等不可索引列)均存在于同一索引中,而innodb不存储大字段于索引页,必然触发回表,explain的extra绝不会出现using index。

覆盖索引不能解决 SELECT * 带来的性能浪费——它根本无法覆盖 SELECT *。 除非你的表只有主键和几个字段,且所有字段都建在同一个联合索引里(现实中几乎不存在),否则 SELECT * 必然触发回表,EXPLAIN 的 Extra 字段绝不会出现 Using index。
为什么 SELECT * 几乎永远无法命中覆盖索引
覆盖索引要求:查询中 SELECT 的每一列、WHERE 条件列、ORDER BY 列,全部落在同一个索引结构内。而 SELECT * 表示“查整行”,意味着要包含所有字段,包括:
-
TEXT、BLOB、超长VARCHAR字段——这些类型不允许出现在索引中(InnoDB 限制单列索引前缀最多 767/3072 字节) - 未显式加入索引的普通字段(如
address、remark) - 主键以外的其他字段,只要没被纳入该索引,就构成回表条件
举个实际例子:users(id, name, age, avatar_base64, created_at),你建了索引 (name, age, id)。执行 SELECT * FROM users WHERE name = 'Alice' 时,优化器发现 avatar_base64 和 created_at 不在索引里,只能先用索引找到 ID,再拿 ID 回聚簇索引读全行——这就是典型的回表。
EXPLAIN 中怎么一眼判断是否被覆盖
只看 EXPLAIN 输出的 Extra 列:
-
Using index✅:真覆盖,不回表,数据全从二级索引页读出 -
Using index condition❌:用了索引下推(ICP),但 SELECT 列不全在索引里,仍需回表 -
Using where+ 没有Using index❌:WHERE 走了索引,但 SELECT 字段导致必须回表 - 空白或只有
Using filesort/Using temporary❌:大概率全表扫描或严重回表
执行 EXPLAIN SELECT * FROM users WHERE name = 'Alice',几乎总是看到后两种结果——这不是配置问题,是语义决定的。
真正能用覆盖索引的写法长什么样
不是“避免 SELECT *”这种泛泛提醒,而是明确按查询反向设计索引+SQL:
- 业务只读
id、name、status?那就写SELECT id, name, status,再建索引(status, id, name)(把WHERE列放最左) - 分页查
title和updated_at,条件是category?索引建(category, updated_at, id, title),SQL 写SELECT id, title, updated_at - 千万别把
avatar_base64或content加进索引——索引体积暴涨,缓存效率暴跌,得不偿失
覆盖索引的价值,是让“查什么”和“存什么”对齐;而 SELECT * 是主动放弃对齐,还指望数据库兜底。
最容易被忽略的一点:即使你给所有字段建了联合索引,只要表里存在 TEXT 或 BLOB,InnoDB 就不会把它们真正存进索引 B+ 树叶子节点,而是只存指针——这意味着,哪怕语法上“所有字段都在索引定义里”,物理上依然无法覆盖。











