<p>SELECT * 在大表中慢是因为需回表查全字段,引发大量随机IO;覆盖索引可避免回表,但须按WHERE、ORDER BY、SELECT字段顺序合理设计,并通过EXPLAIN确认Extra为Using index。</p>

为什么 SELECT * 在大表里特别慢
因为 MySQL 查完索引后,还得根据主键回到聚簇索引里把所有字段捞一遍,这叫“回表”。数据量越大,随机 IO 越多,卡顿越明显。尤其当查询条件走了二级索引,但 SELECT 列又没被这个索引“覆盖”时,必回表。
- 只要
SELECT的字段不在索引定义里,就无法避免回表 -
WHERE用的是idx_user_id,但查了user_name、email、created_at—— 这三个都不在idx_user_id里,100% 回表 - 覆盖索引本质是“让索引本身存够你要的所有数据”,不是“加个索引就能加速”
怎么建覆盖索引:字段顺序和冗余要算清楚
覆盖索引不是把所有字段塞进一个索引就行,顺序错、冗余多,反而拖慢写入、撑大索引体积。
- 必须包含
WHERE条件字段(最左前缀) +ORDER BY字段(如果存在) +SELECT所需字段 - 例如常用查询是
SELECT user_name, email FROM users WHERE user_id = ? ORDER BY created_at DESC,那索引应建为INDEX idx_cover (user_id, created_at, user_name, email) - 别把
TEXT或很长的VARCHAR放进索引——MySQL 会截断或直接报错;用user_name(50)这种前缀长度控制更安全 - 每多一个覆盖索引,INSERT/UPDATE 就多一次 B+ 树维护开销,别无脑堆
怎么确认是不是真用了覆盖索引
光看 EXPLAIN 不够,得盯住 Extra 列有没有 Using index —— 有才是真覆盖;如果出现 Using index condition 或 Using where,说明还是回表了。
- 执行
EXPLAIN SELECT user_name, email FROM users WHERE user_id = 123; - 检查输出中
key是否是你建的覆盖索引名,且Extra是Using index - 如果
Extra是Using index condition; Using where,大概率是WHERE里用了函数(比如WHERE DATE(created_at) = '2024-01-01'),导致索引失效 - 注意:MySQL 8.0+ 对某些隐式类型转换更敏感,
user_id是BIGINT却传字符串,也会让覆盖失效
覆盖索引救不了的几种卡顿
不是所有慢查都能靠覆盖索引解决。它只优化“单行/小范围查找 + 返回固定字段”的场景。
- 查
COUNT(*)全表行数?覆盖索引无效,InnoDB 得扫叶节点,除非加WHERE缩小范围 - 查
LIMIT 1000000, 20?即使有覆盖索引,也要跳过前 100 万行,B+ 树深度变大,延迟照旧高 - 查
GROUP BY配合聚合函数?覆盖索引可能减少回表,但排序和分组本身仍吃 CPU 和内存,得看EXPLAIN里有没有Using filesort或Using temporary - 索引字段重复率极高(比如
status TINYINT只有 0/1),优化器可能直接放弃走索引,哪怕它是覆盖的
覆盖索引不是银弹,它只在“精准定位 + 字段有限”时高效。字段一多、范围一大、逻辑一复杂,就得换思路——分页改游标、聚合加物化视图、冷热分离做归档。这些点容易被忽略,但比硬加索引管用得多。


















