Cardinality是MySQL对索引列不同值个数的采样估算,不精确;应通过ANALYZE TABLE更新统计,并用COUNT(DISTINCT col)/COUNT(*)计算真实区分度,低于0.03慎用索引,高区分度字段须置联合索引最左位。

怎么看 SHOW INDEX 里的 Cardinality 值靠不靠谱
Cardinality 是 MySQL 对索引列“不同值个数”的估算值,不是精确统计。它来自采样(默认约 1/16 的页),所以大表或数据分布突变后容易严重失真——比如明明有 100 万个用户 ID,Cardinality 却显示只有 12 万。
- 查之前先
ANALYZE TABLE your_table,强制更新统计信息(尤其在大批量 INSERT/DELETE 后) - 对比
SHOW INDEX的Cardinality和实际COUNT(DISTINCT column),差 3 倍以上就别信它 -
Cardinality为NULL或1通常意味着索引失效、字段全 NULL 或采样失败,得人工验证
用 SQL 精确算区分度:别只看 Cardinality,要自己算比值
区分度 = COUNT(DISTINCT column) / COUNT(*),这个比值才是决定索引是否值得建的关键数字。0.001(千分之一)和 0.95(95%)带来的查询性能差异是数量级的。
- 直接跑这句:
SELECT COUNT(DISTINCT user_id) / COUNT(*) AS selectivity FROM users;
- 如果字段是联合索引的一部分(如
(del_flag, unit_id, user_id)),必须按顺序拼接计算:SELECT COUNT(DISTINCT CONCAT(del_flag, '-', unit_id, '-', user_id)) / COUNT(*) FROM your_table;
- 区分度 0.8(80%)才建议放联合索引最左位
为什么 del_flag 这种字段绝不能放联合索引最左边
像 del_flag 这类状态位字段,区分度往往低于 0.01(比如 99% 是 0,1% 是 1),放最左会导致整个联合索引几乎无法跳过扫描——优化器大概率放弃走索引,改用全表扫描。
- 正确顺序应把高区分度字段放左:比如
(user_id, unit_id, del_flag),而不是反过来 - 如果必须过滤
del_flag = 0,优先考虑分区表或物理归档,而非依赖索引剪枝 -
WHERE del_flag = 0 AND user_id = ?能走索引,但WHERE user_id = ? AND del_flag = 0才稳定;顺序错一格,执行计划可能完全不同
Python 脚本批量查多个字段的区分度(避免手动重复)
线上表几十个字段,一个个写 SQL 太累。用脚本自动拉出所有索引列 + 实时区分度,比翻 SHOW INDEX 直观得多。
- 核心逻辑就是循环查
COUNT(DISTINCT col)和COUNT(*),再算比值 - 注意避开
TEXT/BLOB字段(COUNT(DISTINCT)会报错),加try/except捕获 - 示例片段:
cursor.execute("SELECT COUNT(*) FROM users")<br>total = cursor.fetchone()[0]<br>cursor.execute("SELECT COUNT(DISTINCT email) FROM users")<br>distinct = cursor.fetchone()[0]<br>print(f"email selectivity: {distinct/total:.4f}")
Cardinality 只是个快照提示,不是判决书。区分度算出来不到 0.03,再漂亮的执行计划也救不了索引;而一个 user_id 区分度 0.999 却被塞在联合索引第三位,等于白建。










