MySQL不走索引可能因索引Cardinality过低,即不同值估算数远小于总行数(如<1%),导致优化器判定扫索引成本高于全表扫描;Cardinality非精确值,需ANALYZE TABLE更新,且须结合EXPLAIN的rows与filtered综合判断。

MySQL不走索引,未必是SQL或索引写错了,很可能是索引本身的区分度太低——Cardinality值过小,优化器直接判定“扫索引比扫表还慢”,于是放弃索引。
Cardinality是什么?它怎么影响索引选择
Cardinality是MySQL统计的索引列“不同值数量”的估算值,存在information_schema.STATISTICS表中。它不是精确计数,但优化器严重依赖它来预估扫描成本:如果Cardinality远小于表总行数(比如只有总行数的1%),优化器会认为该索引过滤性差,走索引后还得回表大量数据,不如全表扫描一次读完。
常见误区是只看有没有建索引、字段是否出现在WHERE里,却忽略这个隐藏指标。
-
SHOW INDEX FROM table_name输出中的Cardinality列就是关键数字 - 它由
ANALYZE TABLE触发更新,不是实时刷新;长时间未分析,值可能严重失真 - 对性别、状态(status)、开关(is_deleted)这类低区分度字段建单列索引,几乎必然被跳过
如何查Cardinality并判断是否异常
先确认当前索引统计是否可信:
ANALYZE TABLE orders;
再查具体索引的Cardinality:
SELECT COLUMN_NAME, CARDINALITY, SUB_PART, INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'orders' AND INDEX_NAME = 'idx_status';
对比几个关键比例:
- 若
CARDINALITY / TABLE_ROWS < 0.01(即低于1%),该索引大概率被跳过 - 若
CARDINALITY = 1或接近1,说明这列几乎全是重复值(如全为0) - 若
CARDINALITY明显小于你肉眼可数的不同值(比如status有5种但显示为2),说明统计过期,需重新ANALYZE
Cardinality低 ≠ 索引没用,但要用对场景
低区分度字段单独建索引意义不大,但组合后可能有效:
- 不要给
status单独建索引,但联合高频查询字段(如(status, create_time))后,Cardinality会显著提升 - 对
is_deleted = 0这类固定值过滤,考虑用生成列+索引:ADD COLUMN is_not_deleted TINYINT AS (if(is_deleted = 0, 1, NULL)) STORED,再给它建索引——此时Cardinality为2,但is_not_deleted = 1能高效定位 - 如果业务上99%查的是
status = 'done',且该值占比极小,可以建函数索引(MySQL 8.0.13+):CREATE INDEX idx_done ON orders ((if(status = 'done', id, NULL))),让优化器看到高区分度的虚拟列
为什么EXPLAIN里key=NULL,但Cardinality看起来还行
这是最容易被忽略的一点:Cardinality只是输入之一,优化器还会结合WHERE条件的实际选择率、回表开销、缓冲池命中率等综合判断。即使Cardinality为10万,但如果查询条件匹配80%的行,优化器仍可能选全表扫描——因为回表80%的主键代价太高。
所以不能只盯着Cardinality,必须配合EXPLAIN的rows和filtered字段看实际预估命中率:
-
rows很大(接近总行数),而filtered很低(比如5%),说明优化器信了Cardinality,但实际过滤效果差 -
rows和filtered都合理,但key仍是NULL,就要怀疑是否有隐式转换、函数、OR等其他失效因素干扰
Cardinality是起点,不是终点;它告诉你“索引有没有潜力”,但最终走不走,得看整个执行计划里所有成本项的博弈结果。


















