真实区分度必须用 count(distinct col) / count(*) 计算,cardinality 仅为采样估算、易失真;单列直接计算,联合索引需 concat 拼接后统计;低区分度字段(如 del_flag)不可放联合索引最左位。

Cardinality 是估算值,不能直接当区分度用;真实区分度必须自己算 COUNT(DISTINCT col) / COUNT(*),否则容易建错索引、拖慢查询。
为什么 SHOW INDEX 的 Cardinality 不可靠
Cardinality 是 MySQL 对索引列“不同值个数”的采样估算(默认约 1/16 的数据页),不是精确统计。大表或批量写入后极易失真:
- 明明有 500 万用户 ID,
Cardinality显示只有 62 万 - 字段全为
NULL或刚加索引未分析,Cardinality可能是NULL或1 - 执行计划里明明该走索引,却选了全表扫描——背后常是优化器信了错误的
Cardinality
查之前务必先运行:ANALYZE TABLE your_table,强制刷新统计信息。
怎么算真实区分度:单列 vs 联合索引
区分度 = 不同值数量 / 总行数。这个比值决定索引是否值得建、以及在联合索引中该放哪一位。
- 单列:直接跑
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 这种状态位,实际数据中往往 99% 是 0,区分度常低于 0.01。放在最左会导致:
- 优化器认为“跳过扫描”收益极低,直接放弃走索引,改用全表扫描
-
WHERE del_flag = 0 AND user_id = ?很可能不走索引;而WHERE user_id = ? AND del_flag = 0才稳定命中 - 即使走了索引,也得回表读大量无关行,I/O 成倍增加
正确做法是把高区分度字段(如 user_id)放最左,del_flag 放末尾,或者考虑物理归档/分区表替代。
线上几十个字段,怎么批量查区分度
手动写 SQL 太累,Python 脚本更实用,核心逻辑就三步:
- 用
SHOW INDEX FROM your_table拉出所有索引列名 - 对每列执行
COUNT(*)和COUNT(DISTINCT col)(注意跳过TEXT/BLOB字段,否则报错) - 用
try/except包住COUNT(DISTINCT),避免某列异常中断整个流程
脚本输出可直接生成表格,一眼看出哪些列区分度高、哪些该删索引——比翻 SHOW INDEX 直观得多。
真正影响执行计划的不是 Cardinality 数字本身,而是它背后代表的选择率(selectivity)。很多人卡在“看了 Cardinality 就以为知道区分度”,结果索引建了等于没建。算一次真实比值,花不了两分钟,但能避开 80% 的低效索引陷阱。











