MySQL常ignore低区分度字段索引,因优化器估算其回表随机I/O成本高于全表顺序扫描;区分度=COUNT(DISTINCT列)/COUNT(*),低于0.01不建议单建索引。

低区分度字段建索引后,MySQL 为什么常 ignore 它?
因为优化器发现走索引比全表扫描还慢。核心矛盾在于:索引本身要读一次 B+ 树页,再回表取完整行(Handler_read_key + Handler_read_next 双重开销),而低区分度字段(比如 gender)命中后仍需访问大量数据行——50% 的匹配率意味着你得遍历一半索引节点,再回表取 50% 的主键对应行,随机 I/O 成本远超顺序扫描。
用 EXPLAIN 看执行计划时,如果 type 是 ALL 而非 ref 或 range,基本就说明优化器已放弃该索引。这不是 bug,是成本估算后的主动规避。
区分度怎么算?多少才算“够高”?
区分度 = COUNT(DISTINCT 列名) / COUNT(*)。数值越接近 1 越好,低于 0.01(即 1%)通常不建议单列建索引。
-
email字段:100 万用户中 98 万不同邮箱 → 区分度 ≈ 0.98 → 适合建索引 -
is_deleted字段:100 万条里 99.5 万为 0 → 区分度 ≈ 0.000005 → 单独建索引几乎无效 -
status字段:若 90% 是 “processing”,10% 是 “done” → 区分度 0.2 → 单独建意义有限,但配合create_time做联合索引可能有效
什么情况下低区分度字段反而能用上索引?
不是“不能建”,而是“不能单建”或“不能乱建”。真正起效的场景往往依赖组合与查询模式:
- 作为联合索引的后缀列,且前缀列区分度足够高 —— 比如
KEY idx_user_id_is_deleted (user_id, is_deleted),查WHERE user_id = ? AND is_deleted = 0就能高效定位 - 查询结果集极小,即使字段区分度低 —— 如
is_deleted = 1只有 0.1% 数据,这时索引可大幅减少扫描量 - 只查索引覆盖字段(
SELECT is_deleted FROM ...),避免回表,type: index也能接受
注意:is_deleted 这类字段单独建索引,在写多读少、软删高频的表里,还会拖慢 INSERT/UPDATE 性能,因每次变更都得维护索引树。
建之前必须验证的三件事
别凭感觉,用数据说话:
- 跑
SELECT COUNT(DISTINCT col)/COUNT(*) FROM table;算真实区分度 - 对目标查询加
EXPLAIN,确认key和rows是否符合预期 - 在生产镜像环境压测,对比加索引前后
Handler_read_rnd_next和查询耗时变化
最容易被忽略的是:低区分度字段一旦进了联合索引,位置错了(比如放最前),整个索引可能失效;而线上表加索引又容易锁表 —— 所以顺序、场景、验证,一个都不能跳。


















