只有EXPLAIN的Extra列显示Using index才算真正使用覆盖索引;Using index condition仅表示索引下推,仍需回表,而SELECT *、ORDER BY字段不匹配、联合索引顺序错误等均会导致无法覆盖。

EXPLAIN 中出现 Using index 才算真正覆盖
看到 EXPLAIN 输出里 Extra 列是 Using index,才能确认当前查询走的是覆盖索引。别被 Using index condition 欺骗——它只表示用了索引下推(ICP),仍要回表取数据;Using where 或空值则说明没覆盖,必然回表。
常见误判点:
-
SELECT *几乎永远不触发Using index,因为二级索引不含所有字段 - 即使表只有 3 列,只要没把它们全建进同一个联合索引,照样回表
-
ORDER BY字段不在索引中或顺序不匹配,哪怕其他字段全覆盖,也可能被迫回表做Using filesort
联合索引字段顺序必须按“等值→范围→SELECT”排
覆盖索引不是字段堆砌,顺序错了等于白建。核心逻辑是:让 MySQL 能用最左前缀一次性定位 + 扫描范围内直接取值。
正确顺序规则:
- WHERE 中的等值条件放最左(如
user_id = ?) - 范围条件(
>、BETWEEN、LIKE 'abc%')放中间,它右边的字段无法走索引 - SELECT 中需要的非聚合字段补在最后(如只要
status和created_at,就加在索引末尾) - 主键自动包含在二级索引中,所以
SELECT id不额外占空间,但显式写上更清晰
反例:CREATE INDEX idx_time_user_status ON orders (created_at, user_id, status) —— 若查询是 WHERE user_id = 123 AND created_at > '2025-01-01',因 user_id 不是最左,该索引完全用不上。
GROUP BY 和分页场景下覆盖索引容易漏掉关键字段
大表 GROUP BY 慢,常被归因为排序,其实更隐蔽的瓶颈是回表。比如 SELECT region, COUNT(*) FROM sales WHERE year = 2025 GROUP BY region,如果索引只含 year 或只含 region,MySQL 就得先扫出主键,再逐个回表取 region 值,才能分组。
必须同时覆盖三类字段:
- WHERE 过滤字段(如
year) - GROUP BY 字段(如
region) - SELECT 中所有非聚合字段(如还有
MAX(created_at),就得把created_at也加进索引)
深分页也是重灾区:SELECT id, name, age FROM users ORDER BY id LIMIT 100000, 10。若只建了 INDEX(id),MySQL 会扫描前 100010 行并全部回表,再丢弃前 100000 行。改成 INDEX(id, name, age) 后,全程只扫索引页,I/O 直接降一个数量级。
TEXT/BLOB 字段和 NULL 处理是静默陷阱
这两个问题不会报错,但会让覆盖索引“假装生效”:
-
TEXT、BLOB类型字段无法被索引存储,哪怕你写进联合索引定义,MySQL 也会静默忽略——导致实际未覆盖,EXPLAIN却可能显示Using index(因为其他字段确实覆盖了,但你要的字段丢了) - 索引列允许
NULL,而 WHERE 条件是IS NULL,优化器不一定走索引,得用EXPLAIN实测验证 - 索引总长度超 3072 字节(InnoDB 默认),建索引会失败;过长的
VARCHAR必须指定前缀,否则设计再好也落地不了
最常被低估的一点:覆盖索引对 COUNT(*) 和分页提升极大,但前提是 WHERE 条件能命中索引,且不能带 SELECT *——否则优化器宁可全表扫描也不愿回表上万次。


















