不适合——重复值多的列不宜单独建普通b+树索引,因其低基数导致空间浪费、写入变慢、优化器弃用;应优先作为组合索引后缀,或改用前缀索引、函数索引(mysql 8.0.13+)等替代方案。

重复值多的列适合建索引吗?
不适合——至少不能只建普通 B+ 树索引。MySQL 的 INDEX 在高重复率列上会严重浪费空间、拖慢写入,且查询优化器大概率不走这个索引。
原因很简单:B+ 树靠有序性加速查找,但像 status(只有 'active'/'inactive')、gender 这类低基数列,索引页里大量指针指向几乎同一组数据,导致树高没优势、范围扫描仍要回表大量行,优化器直接判定“全表扫更快”。
- 常见错误现象:
EXPLAIN显示type=ALL或key=NULL,哪怕你明明建了索引 - 使用场景:这类字段常出现在
WHERE条件末尾(如WHERE user_id = ? AND status = ?),应优先考虑组合索引中作为后缀列 - 参数差异:InnoDB 对重复值不做压缩,每个重复键都存完整记录 + 主键引用,100 万行、90% 是 'active',索引就存约 90 万条几乎一样的
status条目
唯一性差的列怎么建索引才有效?
要么塞进组合索引做后缀,要么改用前缀索引或函数索引(MySQL 8.0.13+)。
组合索引是首选方案:把高区分度列放前面,低区分度列放后面。例如 (user_id, status) 能高效支持 WHERE user_id = 123 AND status = 'active',甚至 WHERE user_id = 123;但反过来 (status, user_id) 就基本失效。
- 性能影响:前缀列区分度越低,组合索引的“剪枝”能力越弱;
(status, created_at)在时间范围查询中可能比单列created_at索引还慢 - 兼容性注意:MySQL 5.7 不支持函数索引,
JSON_EXTRACT()或LOWER()等表达式无法直接建索引,得靠生成列 + 索引模拟 - 实操建议:用
SELECT COUNT(DISTINCT status) / COUNT(*) FROM table;算出选择率,低于 0.01(1%)就别单独建索引
为什么加了索引还是慢?检查这三处
不是所有“有索引”都等于“能用上”。尤其在重复值多的场景下,最容易卡在这三个环节:
- 隐式类型转换:
status是VARCHAR,但查询写成WHERE status = 1,触发全表转换,索引失效 - OR 条件破坏索引:比如
WHERE status = 'active' OR status = 'pending',即使status有索引,也可能退化为全表扫描(除非用UNION拆解) - 统计信息过期:
ANALYZE TABLE table_name;必须定期跑,否则优化器基于陈旧的行数/分布估算,误判索引价值
替代方案:前缀索引和哈希辅助真的有用吗?
前缀索引对重复值多的长文本列(如 url、user_agent)有点用,但对短字段如 CHAR(1) 完全无效;哈希辅助需手动维护,容易错位,生产环境慎用。
真正实用的是:用覆盖索引减少回表,或加冗余字段提升区分度。比如把 status 和 updated_at 合并成生成列 status_updated,再建索引,既保持语义又提高选择率。
- 前缀索引陷阱:
ALTER TABLE t ADD KEY idx_url (url(10));如果前 10 字符全一样(比如全是https://),索引就彻底失去区分能力 - 哈希列风险:用
MD5(status)建索引,但WHERE status = 'active'无法自动转成哈希值匹配,必须同步改查询逻辑 - 更稳的做法:给高频低基数查询加
ENUM类型 + 组合索引,或者用分区表按状态切分(如PARTITION BY LIST COLUMNS(status))
重复值本身不可怕,可怕的是把它当成独立维度去索引。关键永远是:它在查询条件里的位置、和其他字段的联合分布、以及优化器是否真能借力——这些比“建没建索引”重要得多。











