Cardinality过低会导致优化器放弃索引,因其估算回表代价远超全表扫描;可通过SHOW INDEX FROM table_name查看Cardinality值,该值为采样估算,易失真,需结合ANALYZE TABLE更新统计并评估是否应删除孤立低基数索引。

Cardinality太低,优化器直接跳过索引
当 Cardinality 远小于表总行数(比如 500 万行,Cardinality = 3),MySQL 优化器大概率会放弃该索引。这不是 bug,而是它算出来“走索引比全表扫描还慢”——因为要回表几十万次,随机 I/O 成本远超顺序读几页聚簇索引。
怎么查当前索引的Cardinality值?
执行这条命令就能看到具体数值:
SHOW INDEX FROM orders WHERE Key_name = 'idx_status';
重点关注输出里的 Cardinality 列。注意:Cardinality 是采样估算值,不是实时精确统计,容易滞后或失真。如果发现它明显偏低(比如性别字段显示只有 2),但实际数据分布没变,大概率是统计信息没更新。
为什么低基数字段单独建索引基本没用?
- 区分度极低(如
is_deleted、status只有 2–3 个值)→ 每个值对应大量行 → 回表次数爆炸 - 优化器预估要回表 5 次以上,就倾向于放弃索引;而低基数字段一次查询常需回表数万次
- 联合索引中把它放前位,反而会拖垮整个索引效率;应放在后位,配合高区分度字段使用,例如
INDEX(user_id, status) - 别指望
ANALYZE TABLE能彻底解决——它只是重采样,治标不治本;长期要用innodb_stats_persistent = ON+innodb_stats_auto_recalc = ON
强制走索引不是好办法
看到 type=ALL 就想加 FORCE INDEX?先停一下。低基数索引被跳过,本质是设计问题,不是执行计划偶然出错。硬推索引可能让原本 10ms 的查询变成 200ms,尤其在并发稍高时,I/O 压力会集中到磁盘随机读上。真正该做的是:删掉孤立的低基数单列索引,把它整合进覆盖索引,或者从业务侧控制查询粒度。


















