MySQL看到索引却不用,主因是Cardinality过低导致优化器主动跳过;例如status字段仅两值、100万行中Cardinality≈2,选择率极低,优化器判定回表随机I/O代价高于聚簇索引顺序扫描。

MySQL为什么看到索引却不用它?Cardinality太低是主因
不是索引没建,而是优化器主动跳过——Cardinality值远低于总行数时,MySQL会估算该索引过滤效果极差,直接选全表扫描。比如status字段只有'active'、'inactive'两个值,100万行中Cardinality≈2,选择率≈0.000002,优化器认为走B+树再回表,比顺序读还慢。
低基数索引在B+树上实际怎么失效的?
B+树本身不拒绝低基数列,但执行时暴露三重代价:
- 树高度没降多少:即使建了索引,
status这种两值字段,根节点就分两叉,叶子节点密密麻麻指向分散数据页,无法压缩搜索范围 - 回表放大I/O:InnoDB二级索引必须回聚簇索引取完整行,
WHERE status = 'active'命中50万行 → 触发约50万次随机磁盘寻道 - 统计偏差误导优化器:采样不准时,
Cardinality可能被低估(如实际95%是'active',但采样只扫到几个'inactive'页),导致误判选择率,生成错误执行计划
SHOW INDEX里Cardinality不准怎么办?
Cardinality是采样估算值,不是实时精确统计。大表上尤其容易失真:
- 用
ANALYZE TABLE table_name强制重采样,但会加锁,别在高峰期跑 - MySQL 8.0+可配合直方图:
ANALYZE TABLE table_name UPDATE HISTOGRAM ON status,提升低基数列的统计精度 - 别只看
Cardinality绝对值,算选择率:SELECT COUNT(DISTINCT status) / COUNT(*) FROM t,selectivity 基本可判定单列索引无效
想让低基数字段“有用”,得绕开单列索引陷阱
硬给gender或is_deleted建单列索引,99%是白忙。真正可行路径只有两条:
- 组合索引中把它放后面:
INDEX (created_at, status)——高基数字段created_at先切出时间范围,status再在小结果集里过滤 - 覆盖查询:确保
SELECT只查索引包含的列,例如INDEX (status, name)配SELECT name FROM t WHERE status = 'X',Extra出现Using index才真正受益
最常被忽略的一点:复合索引里低基数字段放最左,等于废掉整个索引——既破坏最左前缀匹配,又让JOIN或ORDER BY无法利用后续字段的有序性。



















