覆盖索引是查询“只读索引、不回表”的状态,核心看EXPLAIN的Extra字段:出现Using index或Using where; Using index即为覆盖索引,而SELECT *几乎无法触发,因聚簇索引才存全字段。

覆盖索引不是一种新索引类型,而是查询能否“只读索引、不回表”的状态判断——只要 EXPLAIN 的 Extra 列出现 Using index,就说明当前查询走的是覆盖索引。
怎么确认一个查询是否用了覆盖索引
核心看 EXPLAIN 输出的 Extra 字段:
- 出现
Using index→ 真正的覆盖索引(只读二级索引叶子节点) - 出现
Using where; Using index→ 也是覆盖索引,但 WHERE 中部分条件在引擎层做过滤(比如索引下推) - 只有
Using where或Using filesort→ 没覆盖,大概率要回表
注意:SELECT * 几乎不可能触发覆盖索引,因为聚簇索引才存全字段;即使你建了 KEY idx_a_b (a,b),SELECT a,b FROM t WHERE a=1 才能覆盖,SELECT a,b,c 就不行(c 不在索引里)。
为什么联合索引顺序影响覆盖效果
覆盖索引依赖最左前缀匹配,且必须包含查询中所有涉及的字段:WHERE 条件字段、SELECT 字段、ORDER BY 字段、GROUP BY 字段都算在内。
- 建了
KEY idx_city_status_amount (city, status, amount) -
SELECT city, status FROM orders WHERE city = 'Beijing'→ 覆盖 ✅ -
SELECT city, amount FROM orders WHERE status = 'paid'→ 不覆盖 ❌(status不是最左,无法走索引) -
SELECT city, status FROM orders WHERE city = 'Beijing' ORDER BY amount→ 不覆盖 ❌(ORDER BY amount虽在索引里,但不在 WHERE 后连续位置,无法利用索引有序性)
真正能覆盖排序的写法是:WHERE city = 'Beijing' ORDER BY status, amount —— 必须严格按索引定义顺序连续使用。
哪些字段该放进覆盖索引,哪些不该
覆盖索引本质是用空间换 I/O 效率,所以字段选择很关键:
- 优先放高频查询中固定出现的字段组合,比如订单列表页常查
order_no、status、amount、created_at - 避免放入大字段:
TEXT、BLOB、长VARCHAR会显著增大索引体积,抵消性能收益 - InnoDB 辅助索引叶子节点天然包含主键值,所以
SELECT id总是被覆盖(只要索引存在),不用显式加到联合索引里 - 别为覆盖而覆盖:如果某查询一年只跑一次,不值得单独建索引;但如果每秒调用上千次,哪怕多占几 MB 空间也值得
示例建表语句:CREATE INDEX idx_status_created ON orders(status, created_at, order_no, amount); —— 这个索引能覆盖 SELECT order_no, amount FROM orders WHERE status = 'shipped' ORDER BY created_at LIMIT 20。
常见误判和隐形坑
看起来像覆盖,实际没生效的情况很常见:
-
SELECT COUNT(*) FROM t WHERE a = 1:如果a是普通索引,InnoDB 可能用它覆盖(因只需计数),但如果a允许 NULL,且大量值为 NULL,优化器可能放弃索引改走聚簇索引 - 隐式类型转换:
WHERE mobile = 13800138000(mobile是VARCHAR)→ 触发全索引扫描,Using index会消失 - 函数操作:
WHERE DATE(created_at) = '2026-01-01'→ 索引失效,自然也不覆盖 - 统计信息过期:
ANALYZE TABLE orders没执行过,优化器可能误判成本,选错索引
最隐蔽的一点:覆盖索引对 UPDATE / DELETE 语句同样有效(只要 WHERE 条件能走覆盖索引),但很多人只关注 SELECT,忽略了写操作也能受益。


















