覆盖索引生效需SELECT、WHERE、ORDER BY、GROUP BY所有字段均落在同一二级索引中,EXPLAIN中须紧盯Extra列:出现“Using index”或“Using where; Using index”表示真覆盖,而“Using index condition”或空值则未覆盖;SELECT *几乎必然导致回表。

能,但必须让 SELECT、WHERE、ORDER BY、GROUP BY 所有字段都落在同一个二级索引里——漏一个,就回表。
EXPLAIN 中怎么一眼判断是否真覆盖
别只看 type 或 key,死盯 Extra 列:
-
Using index→ ✅ 真覆盖,不回表 -
Using where; Using index→ ✅ 也覆盖,ICP + 覆盖同时生效 -
Using index condition→ ❌ 只用了索引下推,仍要回表取非索引字段 - 空值、
Using where、Using filesort→ ❌ 没覆盖,大概率在回表
特别注意:SELECT * 几乎永远触发不了 Using index,因为二级索引叶子节点不含所有字段;哪怕你建了 (a,b,c) 索引,SELECT * 也会让优化器放弃覆盖路径。
联合索引字段顺序怎么排才真正有效
顺序不是随便堆的,它决定能不能过滤、能不能覆盖、会不会失效:
- 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 - 隐式类型转换:
WHERE mobile = 13812345678(mobile 是 VARCHAR)→ 改成WHERE mobile = '13812345678' - 左模糊查询:
WHERE name LIKE '%ton'→ 索引无法定位起点,扫描量过大时优化器宁可全表扫 - TEXT/BLOB 字段直接加进索引 → 建索引会失败或被静默截断,必须用前缀,如
email(100)
哪怕索引定义完全匹配,只要 WHERE 或 SELECT 里出现上述任一情况,EXPLAIN 的 Extra 就不会显示 Using index。
覆盖索引的代价容易被低估
它不是越宽越好,写入和内存开销会真实上涨:
- 每多一个
VARCHAR(200)字段,B+ 树节点变大,buffer pool 缓存效率下降 - INSERT/UPDATE/DELETE 都要同步更新该索引,字段越多,写放大越明显
- 高频更新字段(如
status、updated_at)放进覆盖索引,会显著拖慢写性能 - 索引总长度不能超 3072 字节(InnoDB 默认),长字段必须指定前缀,否则
CREATE INDEX直接报错
真正难的不是建索引,而是让索引字段顺序、类型、长度,和实际查询的 WHERE 条件、SELECT 列表、ORDER BY 依赖严丝合缝地咬合——差一个 collation、多一个 NULL 判断、少一个等值条件,优化器就可能绕开它。


















