MySQL优化器基于成本估算选择扫描方式,当“索引扫描+回表”总代价高于全表扫描时,即使字段都在索引中,仍选ALL;关键看filtered值、匹配行占比及IO开销权衡。

为什么 EXPLAIN 显示没走覆盖索引,但字段明明都在索引里?
不是索引建得不对,而是 MySQL 优化器判断「走索引 + 回表」的总成本高于「全表扫描」。尤其当查询返回行数占比高(比如 > 20%)、或需要回表取大量列时,优化器会主动放弃索引。例如:SELECT name, age, position FROM employees WHERE name LIKE 'Li%',即使有 idx_name_age_position 覆盖全部三列,若匹配行太多,type 仍可能是 ALL。
关键点在于:覆盖索引生效的前提是「索引扫描本身能显著减少 IO」。如果扫描索引页 + 回表(哪怕不回表,仅扫描二级索引叶子节点)的随机读开销,超过顺序读全表的成本,优化器就选全表。
- 用
EXPLAIN FORMAT=JSON查看used_columns和filtered字段,确认实际命中率 -
filtered值低于 10 表示匹配行比例极低,此时更可能走索引;高于 50 则大概率放弃 - SSD 上随机 IO 成本下降,但优化器默认参数仍按机械盘估算,可通过
SET SESSION optimizer_switch='use_index_extensions=on'微调(谨慎)
索引下推 ICP 是什么,它和覆盖索引冲突吗?
ICP(Index Condition Pushdown)是 MySQL 5.6+ 引入的优化:把原本在 Server 层过滤的 WHERE 条件,下推到存储引擎层,在索引遍历过程中提前过滤,减少回表次数。它不替代覆盖索引,而是和覆盖索引协同工作。
典型场景是复合索引 + 非最左前缀条件。例如索引 (name, age, position),查询 WHERE name = 'Li' AND age > 25 AND position = 'dev':
- 没有 ICP:引擎只用
name = 'Li'定位索引范围,把所有匹配的(name,age,position)行回表后,Server 层再判断age > 25和position = 'dev' - 开启 ICP:引擎在索引页内直接用
age > 25过滤,只对满足全部条件的索引项执行回表(或直接返回,如果是覆盖查询) - ICP 是否启用由
optimizer_switch中的index_condition_pushdown控制,默认 on
哪些情况下 ICP 不生效,导致看似该走索引却没走?
ICP 依赖存储引擎支持且受条件类型限制。InnoDB 支持大部分,但以下情况会退化为 Server 层过滤,甚至让优化器彻底放弃索引:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 对索引列使用函数,如
WHERE UPPER(name) = 'LI'→ ICP 失效,且索引失效 - 隐式类型转换,如
WHERE name = 123(name是 varchar)→ 索引失效,ICP 无从谈起 - 涉及
OR且部分分支无法下推,如WHERE name = 'Li' OR age = 30→ 整个条件无法下推 - 使用
!=或NOT IN时,ICP 可能不触发,优化器倾向全表扫描 - LIKE 左模糊(
LIKE '%Li')不走索引,ICP 自然无效
验证方式:在 EXPLAIN 结果中看 Extra 字段是否含 Using index condition。没有,说明 ICP 没参与。
覆盖索引 + ICP 组合优化的真实代价在哪里?
二者叠加确实能压低 IO,但有个隐蔽瓶颈:索引页内比较操作本身消耗 CPU。当索引列数据量大、条件复杂(比如多层嵌套函数),ICP 在引擎层做的过滤反而拖慢单行处理速度,尤其在高并发小查询场景下,CPU 成为瓶颈。
此时 EXPLAIN 可能显示走了索引(type = range 或 ref),但 rows 和 filtered 的乘积远大于实际返回行数,说明大量索引项被 ICP 过滤掉——这不是失败,而是「用 CPU 换 IO」的显性化。
真正容易被忽略的是:索引列顺序必须严格匹配查询模式。比如查询 WHERE age = 25 AND position = 'dev',即使有 (name, age, position) 索引,因跳过最左列 name,ICP 和覆盖都失效。必须重建为 (age, position) 或 (age, position, name) 才起作用。

















