EXPLAIN中type为range或ref且Extra含Using index condition才算真正走索引;NULL值占比过高或SELECT *导致回表开销大时,优化器会选全表扫描。

看EXPLAIN的type和Extra字段是否匹配索引路径
直接执行EXPLAIN是第一步,但光看possible_keys有值不够——关键得盯住type和Extra:
• 如果type是ALL,说明优化器彻底放弃索引,走全表扫描;
• 如果type是range或ref,且key显示具体索引名(比如idx_col1),再配合Extra里出现Using index condition或Using index,才算真正走了索引;
• 如果type是index(全索引扫描),说明它读了整个二级索引页,但没回表——这不算“失效”,只是效率未必最优,尤其当索引列不覆盖查询所需字段时。
检查NULL值占比与优化器成本估算是否失衡
MySQL不会因为写了IS NOT NULL就硬用索引,它会预估“走索引+回表”和“直接扫聚簇索引”哪个更便宜:
• 当col1 IS NOT NULL匹配的行数占全表比例过高(比如 >20%~30%),优化器大概率选ALL;
• 反过来,如果该列NULL值占比极高(如95%),IS NOT NULL实际只命中少量行,range扫描就容易触发;
• 验证方法:用SELECT COUNT(*) FROM t WHERE col1 IS NULL和SELECT COUNT(*) FROM t算出比例,比凭感觉更可靠。
确认查询字段是否触发回表开销
即使WHERE col1 IS NOT NULL能走索引,SELECT *仍可能让优化器弃用它:
• 因为二级索引里只有col1和主键,SELECT *需要拿着每个匹配的主键去聚簇索引里捞整行数据(即回表);
• 若预估要回表的行数太多,随机I/O代价远超顺序扫描,优化器就降级;
• 解法很直接:改成只查索引列,例如SELECT col1 FROM t WHERE col1 IS NOT NULL,这时Extra若出现Using index,说明是覆盖索引,基本稳走。
验证复合索引中IS NOT NULL作用于哪一列
对联合索引INDEX (a, b, c),IS NOT NULL的位置决定它能不能被利用:
• WHERE a IS NOT NULL:可以定位B+树起始位置,type常为range;
• WHERE b IS NOT NULL:无法跳过a的任意值,索引失效,退化为ALL;
• WHERE a = 1 AND b IS NOT NULL:前导列固定后,b IS NOT NULL可在对应子树内range扫描,可行;
• 注意:IS NULL在联合索引里也遵循同样规则,但它天然能利用B+树最左端NULL聚集的特性,所以有时比IS NOT NULL更稳定。
IS NOT NULL要扫一大片”+“我还得挨个回表取数据”这个组合时,默默选择了更省事的全表扫描。调优时优先看比例、看覆盖、看执行计划里的真实路径,而不是纠结“为什么写了索引却不走”。


















