索引选择性是不重复索引值数量与表总行数的比值,决定MySQL能否靠索引快速缩小查找范围;例如email字段选择性≈0.999,而status仅0.000002,后者即使建索引也常被优化器弃用。

索引选择性到底是什么,为什么它比“有没有索引”更重要
索引选择性不是“这个字段能不能建索引”,而是“建了之后,MySQL 能不能靠它快速缩小查找范围”。它的本质是:不重复的索引值数量 ÷ 表总行数。比如 email 字段有 100 万行、99.9 万不同值,选择性 ≈ 0.999;而 status 只有 'active' 和 'inactive' 两个值,选择性只有 0.000002 —— 后者即使建了索引,优化器也大概率弃用。
- 选择性 > 0.1 通常值得建索引;
- 唯一索引(如主键、
UNIQUE)选择性恒为 1,效果最稳 -
SHOW INDEX FROM table_name中的Cardinality是采样估算值,不准,不能直接当选择性用 —— 要自己算:SELECT COUNT(DISTINCT col) / COUNT(*) FROM table_name
复合索引里字段顺序怎么排?别再凭感觉写了
字段顺序决定索引能覆盖哪些查询,核心原则就一条:把选择性高的列放前面。因为 MySQL 的 B+ 树索引是先按第一列排序,再在相同值内按第二列排序,依此类推。如果高选择性列在后,前导列区分度低,树的分支就“胖”而不“深”,扫描行数反而更多。
- 错误示范:
INDEX idx_status_created_at (status, created_at)——status可能只有 3–5 个值,开头就卡死在大块数据里 - 正确做法:
INDEX idx_created_at_status (created_at, status)—— 时间戳天然高选择性,能快速定位到某天/某小时的数据块,再筛状态 - 注意:如果查询中含范围条件(如
created_at > '2025-01-01'),它后面的字段就失效了 —— 所以范围列尽量放最后
为什么 EXPLAIN 显示走了索引,但查询还是慢?可能是“假走索引”
优化器说“我用了索引”,不代表真高效。常见假象是:索引被选中,但 rows 值巨大,或者 Extra 出现 Using where; Using index 却仍回表多次。根本原因往往是索引没覆盖查询所需字段,或选择性太差导致实际扫描行数远超预期。
- 查
SELECT name, email FROM users WHERE status = 'active',只给status建索引 → 必须回表取name和email→ 慢 - 改成覆盖索引:
INDEX idx_status_name_email (status, name, email)→ 全部字段都在索引里,无需回表 - 用
EXPLAIN FORMAT=JSON看used_columns和key_parts,确认是否真正用到了全部索引字段
基数不准怎么办?别让过期统计拖垮执行计划
MySQL 的索引统计(Cardinality)默认靠采样估算,且只在数据变更超 1/M(默认 M=20,即 5%)时才自动更新。这意味着:大批量删旧数据后,索引统计还显示“1000 万行”,但实际只剩 10 万,优化器仍按老数据做成本判断,可能错选全表扫描。
- 手动更新统计:
ANALYZE TABLE table_name(InnoDB 下轻量,不锁表) - 长期高频写入场景,建议开启持久化统计:
SET GLOBAL innodb_stats_persistent = ON,避免重启后丢失 - 极端情况可临时强制索引:
SELECT * FROM t USE INDEX (idx_col),但这是兜底手段,不能替代统计修复
真正容易被忽略的点是:索引选择性不是静态指标,它随数据分布实时变化。今天高选择性的字段,明天批量导入相同值后可能就废了——所以定期用 SELECT COUNT(DISTINCT...) 抽样验证,比盲目加索引更有效。










