
为什么 SELECT * 让索引失效得特别快
因为 MySQL 的二级索引(非聚簇索引)只存索引列 + 主键值,查 * 时必然要拿着主键去聚簇索引里再查一遍整行数据——这就是“回表”。一次查询变两次 B+ 树查找,I/O 翻倍,性能断崖下跌。
- 只要
SELECT列不全在索引里,就一定会回表;SELECT id, name却建了INDEX(name)?照样回表,因为没包含id - 覆盖索引的前提是:查询涉及的所有列(包括
WHERE、ORDER BY、SELECT中的列)都出现在同一个索引中 - 注意
ORDER BY和GROUP BY列也得进索引,否则排序/分组可能触发 filesort,抵消覆盖优势
怎么写出真正能覆盖的联合索引
顺序不是随便排的。MySQL 索引遵循最左前缀原则,但“覆盖”还要求把查询中所有用到的字段都塞进去,且顺序要兼顾过滤性 + 覆盖性。
- 先放
WHERE等值条件列(如status = 'active'),再放范围列(如created_at > '2024-01-01'),最后放SELECT和ORDER BY列(如user_id, nickname) - 别把
TEXT或大VARCHAR放索引里——会拖慢写入,还可能触发Row size too large错误 - 用
EXPLAIN看Extra字段:出现Using index才算真覆盖;如果还有Using where; Using index,说明 WHERE 条件用了索引下推(ICP),但仍是覆盖的
示例:查活跃用户最新 10 个昵称,SELECT nickname FROM users WHERE status = 'active' ORDER BY created_at DESC LIMIT 10 → 建索引 INDEX(status, created_at, nickname)
ORDER BY 强制走索引却没覆盖?常见陷阱
很多人建了 INDEX(a, b),查 WHERE a = ? ORDER BY b 是快了,但一加 SELECT c 就回表——因为 c 不在索引里。更隐蔽的是方向不一致。
-
ORDER BY b ASC, c DESC这种混合方向,在 MySQL 8.0 之前无法用索引排序,哪怕INDEX(b, c)存在也会触发Using filesort -
WHERE a IN (1,2,3) ORDER BY b:IN 是等值列表,但 MySQL 对多值 IN 的索引使用有限,b很可能无法用于排序,除非整个组合被覆盖 - 用
EXPLAIN FORMAT=JSON查used_key_parts,确认排序字段是否真的被索引覆盖,而不是靠临时表或 filesort
什么时候覆盖索引反而更慢
索引不是越多越好。宽索引(列多、字节大)会显著拖慢 INSERT/UPDATE,并占用更多 Buffer Pool,挤占热数据空间。
- 对高频更新的表(比如每秒百次写入),加一个 5 列的覆盖索引,可能让写性能掉 30%+,得不偿失
- 如果查询只返回几行,但覆盖索引要扫描上万行(比如
WHERE status = 'pending'匹配太多),还不如走主键 + 条件过滤快 - 注意
NULL值:索引列允许NULL时,WHERE col IS NULL可走索引,但IS NOT NULL在旧版本可能不走,影响覆盖判断
真正省事的办法,是用 pt-query-digest 或慢日志找出 top 查询,针对它们逐个分析 SELECT 列和 WHERE 条件,再决定要不要建覆盖索引——别一上来就给整张表建个全字段索引。










