覆盖索引需索引结构、查询写法、执行计划三者对齐,EXPLAIN中仅Extra列含“Using index”或“Using where; Using index”才真覆盖;SELECT *、隐式类型转换、函数、TEXT/BLOB、左模糊等均使其失效。

覆盖索引能显著缓解高并发下的回表压力,但前提是索引结构、查询写法和执行计划三者完全对齐;否则看似建了索引,实际仍在高频回表。
EXPLAIN 中如何一眼确认是否真覆盖
别看 type 或 key,只盯 Extra 列:
-
Using index→ ✅ 真覆盖,整条查询只走索引树,不回表 -
Using where; Using index→ ✅ 也覆盖,ICP + 覆盖同时生效 -
Using index condition→ ❌ 只用了索引下推,仍要回表取非索引字段 - 空值、
Using filesort、Using temporary→ ❌ 没覆盖,大概率在回表
特别注意:SELECT * 几乎永远无法触发 Using index,因为二级索引叶子节点不含所有字段;哪怕你建了 (aadhar_num, id, status) 索引,SELECT * 也会让优化器放弃覆盖路径。
联合索引字段顺序必须按查询模式排列
顺序不是堆字段,而是决定能否过滤、能否覆盖、会不会失效的关键:
- WHERE 中的等值条件(如
aadhar_num = ?)必须放最左 - 范围条件(如
created_at > ?)紧接其后,它右边的字段只能用于覆盖,不能用于查找 - SELECT 字段(如
status,created_at)补在末尾;主键(如id)不用显式写,InnoDB 自动存 - ORDER BY 字段如果不在 WHERE 条件里,也得塞进索引末尾,否则触发
Using filesort
反例:INDEX idx_created_aadhar (created_at, aadhar_num) 对 WHERE aadhar_num = '123456789012' 完全无效——跳过最左列,整个索引用不上。
高并发下最容易踩的隐性坑
这些错误不会报错,但会让覆盖索引“悄悄失效”,并发一上来延迟就飙升:
- 隐式类型转换:应用传数字
WHERE aadhar_num = 123456789012(字段是VARCHAR),MySQL 强制转字符串,绕过 B-Tree 查找 → 改成WHERE aadhar_num = '123456789012' - SELECT 中用了函数:
SELECT UPPER(name)即使name在索引里,也无法命中覆盖(MySQL 8.0+ 需显式建函数索引) - TEXT/BLOB 字段出现在 SELECT 列表 → 必然回表(前缀索引仅支持定义,不支持覆盖)
- 左模糊查询:
WHERE name LIKE '%ton'→ 索引无法定位起点,优化器可能直接放弃索引
哪怕索引定义完全匹配,只要 WHERE 或 SELECT 里出现上述任一情况,EXPLAIN 的 Extra 就不会显示 Using index。
覆盖索引不是越宽越好
二级索引叶子节点只存索引列 + 主键,所以“宽”索引虽利于覆盖,但会带来真实代价:
- 主键是
BIGINT(8 字节)比INT(4 字节)会让所有二级索引变大一倍 -
VARCHAR(255)列放进索引,实际按最大长度预留空间(utf8mb4 下更甚),极易撑爆索引页,导致 B+ 树层级加深 - 索引列越多,单页能存的键值对越少,范围扫描效率反而下降
优先追加高频查询的小字段(如 status, type, is_deleted),避免把 description、content 这类大字段硬塞进索引。


















