优化器跳过低区分度字段索引是因为成本估算显示回表开销高于全表扫描;区分度低于0.03(如gender、status)时不应放联合索引最左,而应将高区分度字段置前,低区分度字段作辅助列或用于覆盖索引、分区等替代方案。

为什么优化器会跳过低区分度字段的索引
不是索引建错了,是优化器算了一笔账:如果 gender 只有 'M'/'F' 两个值,走索引查出 50% 的行,再回表取数据,I/O 成本可能比直接全表扫描还高。MySQL 在 ANALYZE TABLE 后拿到的 Cardinality 值偏低(比如 COUNT(DISTINCT gender) / COUNT(*) ≈ 0.02),就会倾向放弃该索引。
怎么判断一个字段是否属于低区分度
别猜,查数据:
- 运行
SHOW INDEX FROM table_name,看Cardinality列——它反映 MySQL 统计出的该列唯一值数量,但不绝对准确 - 更可靠的是执行
SELECT COUNT(DISTINCT col) / COUNT(*) FROM table_name,结果低于0.03就算低区分度(比如is_deleted、status只有 2~4 个值) - 注意:如果该列 NULL 值占比很高,也会拉低有效区分度,
COUNT(col)和COUNT(*)差距大时要单独评估
低区分度字段该不该放联合索引最左位
不该,除非它能先筛掉 90%+ 的数据。常见错误是把 status 放在 (status, user_id) 最左,结果查 WHERE status = 'active' 扫了上百万行——因为 user_id 的高区分度根本没机会发挥。
正确做法是反着来:
- 把高区分度字段放最左,比如
user_id(百万级唯一值) - 低区分度字段放右侧,作为“筛选后排序/分组”的辅助列,例如
(user_id, status) - 这样
WHERE user_id = ? AND status = ?能用上全部两列;只查user_id也能用上第一列 - 如果业务常按
status+ 时间范围查,可考虑(status, created_at),但前提是status过滤效果足够好(比如'draft'只占 0.5%)
实在绕不开低区分度字段查询怎么办
硬加索引没用,得换思路:
- 优先用覆盖索引减少回表,比如
SELECT id, status FROM orders WHERE status = 'shipped',建INDEX idx_status_cover (status, id),避免查到行后再去聚簇索引捞数据 - 小表或低频查询,接受全表扫描——优化器选它,往往就是当前最优解
- 高频且必须按低区分度字段查(如后台导出所有
is_deleted = 1记录),考虑分区,比如按is_deleted做 LIST 分区,让物理存储分离 - 极少数场景(如性别画像分析),可建冗余列+函数索引(MySQL 8.0+):
ALTER TABLE users ADD COLUMN gender_code TINYINT;,再CREATE INDEX idx_gender_code ON users(gender_code),用应用层映射代替原字段直查
真正容易被忽略的点是:低区分度本身不是问题,问题是把它当成了“筛选主力”。它更适合做索引尾巴、分区依据或业务层兜底过滤,而不是指望它扛起定位任务。


















