Cardinality是MySQL对索引列不同值个数的采样估算值,优化器据此预估过滤行数;若其过低(如500万行中仅3个值),则判定回表代价远超全表扫描而弃用索引。

MySQL 优化器不是“不走索引”,而是算出来“走索引更慢”——Cardinality过低是它做出这个判断的关键依据之一。
Cardinality 是什么,为什么它影响索引选择
Cardinality 是 MySQL 对索引列“不同值个数”的采样估算值,不是精确统计。优化器靠它预估一个查询条件能过滤掉多少行。比如 status 列只有 'pending'、'paid'、'cancelled' 三个值,总行数 500 万,那理想 Cardinality 应该是 3;优化器看到这个数字,就会认为查 WHERE status = 'paid' 要扫描约 167 万行,再加回表,随机 I/O 开销远超顺序读聚簇索引页。
-
Cardinality不是实时更新的,默认靠采样(innodb_stats_sample_pages=20),大表或数据批量变更后极易失真 - 它只影响“是否选这个索引”,不影响索引本身能否用(即语法上仍合法)
- 联合索引中前导列
Cardinality过低(如(status, create_time)),整个索引的区分能力会被拉垮
怎么确认是不是 Cardinality 导致的全表扫描
别猜,直接查执行计划和统计信息:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 执行
EXPLAIN SELECT * FROM orders WHERE status = 'paid',看key是否为NULL、type是否为ALL、rows是否接近总行数 - 执行
SHOW INDEX FROM orders WHERE Key_name = 'idx_status',重点看输出里的Cardinality列 - 手动验证真实基数:
SELECT COUNT(DISTINCT status) FROM orders,和Cardinality值对比;若偏差巨大(比如手动查是 3,SHOW INDEX显示是 100),就是统计失真
临时修复和长期规避策略
硬加 FORCE INDEX 可能让查询更慢,尤其在并发场景下会把 I/O 压力集中到磁盘随机读。真正该做的是:
- 临时更新统计:
ANALYZE TABLE orders(注意:大表会加读锁,避开业务高峰) - 长期开启持久化统计:
SET GLOBAL innodb_stats_persistent = ON+SET GLOBAL innodb_stats_auto_recalc = ON - 删除孤立的低基数单列索引(如只为
is_deleted或gender单独建的索引) - 改用联合索引,并把低基数列放在后面:
INDEX(user_id, status)比INDEX(status, user_id)有效得多 - 如果查询确实需要高频过滤低基数字段,优先考虑覆盖索引(避免回表),例如
INDEX(status, id, amount)配合SELECT id, amount FROM ... WHERE status = ?
最常被忽略的一点:Cardinality 失真 + 隐式类型转换(比如 VARCHAR 字段写成 WHERE status = 0)会形成双重误判,优化器连“尝试走索引”这一步都跳过了。

















