覆盖索引真正生效的唯一判断标准是EXPLAIN中Extra列为Using index;Using index condition或空值、Using where等均表示未完全覆盖,仍会回表。

覆盖索引真正生效,只看 EXPLAIN 的 Extra 列是否出现 Using index —— 其他任何提示(包括 Using index condition)都意味着还在回表。
怎么一眼确认覆盖索引是否真生效
别信索引名、别信字段全写了、别信 type=ref 或 key 有值。唯一可信的是 EXPLAIN 输出中 Extra 列的值:
-
Using index→ ✅ 真覆盖,不回表 -
Using where; Using index→ ✅ 也覆盖,ICP + 覆盖同时起效 -
Using index condition→ ❌ 只用了索引下推,仍要回表取非索引字段 - 空值、
Using where、Using filesort→ ❌ 没覆盖,大概率在回表
特别注意:SELECT * 几乎永远触发不了 Using index,因为二级索引叶子节点不含所有字段;哪怕你建了 (a,b,c) 索引,SELECT * 也会让优化器放弃覆盖路径。
联合索引字段顺序怎么排才不白建
顺序不是堆字段,它决定能不能过滤、能不能覆盖、会不会失效。核心是让 MySQL 能用最左前缀一次性定位 + 扫描范围内直接取值:
- WHERE 中的等值条件(如
user_id = ?)放最左 - 范围条件(如
created_at > ?)紧接其后,它右边的字段只能用于覆盖,不能用于查找 - SELECT 字段(如
status, amount)补在末尾;主键(如id)不用显式写,InnoDB 自动存 - ORDER BY 字段如果不在 WHERE 条件里,也得塞进索引末尾,否则触发
Using filesort
反例:INDEX idx_created_status (created_at, status) 对 WHERE status = 1 无效——跳过最左列,整个索引用不上。
哪些写法会让覆盖索引“悄悄失效”
建了索引 ≠ 生效。这些常见操作会让 MySQL 直接弃用覆盖路径:
- 对索引字段用函数:
WHERE YEAR(created_at) = 2025→ 改成WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01' - 隐式类型转换:
WHERE mobile = 13812345678(mobile是VARCHAR)→ 改成WHERE mobile = '13812345678' - 左模糊查询:
WHERE name LIKE '%ton'→ 索引无法定位起点,优化器可能直接全表扫 - TEXT/BLOB 字段直接加进索引 → 建索引会失败或被静默截断,必须用前缀,如
email(100)
哪怕索引定义完全匹配,只要 WHERE 或 SELECT 里出现上述任一情况,EXPLAIN 的 Extra 就不会显示 Using index。
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加进索引)
深分页(如 LIMIT 1000000, 20)同样依赖覆盖:若 ORDER BY created_at 后没带 id,又没包含在索引里,就会触发大量回表或文件排序。


















