在基于MySQL的业务系统中,new_pool表常用来存储内容池数据,其中chlid字段标记内容所属频道。当查询条件写成chlid不等于news_top且不等于news_ent时,通过explain查看执行计划,经常会发现type列显示为ALL,也就是全表扫描。这种现象背后涉及MySQL优化器对索引成本的判断逻辑。

问题重现
假设new_pool表在chlid字段上建有普通索引,我们执行如下查询:
EXPLAIN SELECT * FROM new_pool WHERE chlid <> 'news_top' AND chlid <> 'news_ent';
执行计划结果中,key字段为NULL,type为ALL,说明没有使用索引,进行了全表扫描。
为什么索引会失效
优化器的成本估算
MySQL优化器在选择执行路径时,会比较走索引和全表扫描的预估成本。对于chlid <> 'news_top' AND chlid <> 'news_ent'这样的条件,绝大多数行都满足不等于这两个值,优化器认为需要返回表里的大部分数据。
- 如果使用二级索引,要先扫描索引树找到符合条件的索引项,再回表取数据,随机IO较多。
- 如果直接全表扫描,采用顺序IO读取所有行并过滤,总成本反而更低。
不等于条件的语义限制
普通B+树索引擅长做等值匹配、范围查询。不等于操作无法利用索引的有序性快速定位区间,优化器通常将其视为低选择性条件。
如何验证与解决
使用覆盖索引
如果查询只用到索引列,可以建立覆盖索引避免回表,让优化器愿意走索引:
-- 假设只需查询 id 和 chlid ALTER TABLE new_pool ADD INDEX idx_chlid_id (chlid, id); EXPLAIN SELECT id, chlid FROM new_pool WHERE chlid <> 'news_top' AND chlid <> 'news_ent';
改写SQL逻辑
当频道值有限时,用IN列出需要的值代替不等于:
-- 假设其余频道只有 news_sport 和 news_finance
EXPLAIN
SELECT * FROM new_pool
WHERE chlid IN ('news_sport', 'news_finance');
| 写法 | 索引使用情况 | 适用场景 |
|---|---|---|
| chlid <> A AND chlid <> B | 易全表扫描 | 排除值极少、剩余数据极多 |
| chlid IN (...) | 可用索引 | 目标值明确且较少 |
| 覆盖索引+不等于 | 可能用索引 | 只需索引列字段 |
总结
new_pool表中chlid不等于news_top或news_ent时出现全表扫描,核心原因是优化器基于成本选择顺序IO全表读取。通过减少回表、改写条件或调整索引设计,可以有效规避这一性能陷阱。