MySQL 索引基数、选择性与索引设计的关系

在 MySQL 的面试中,索引相关问题几乎从未缺席,而“索引基数”和“选择性”这两个概念,往往是区分初级和高级开发者的分水岭。很多人能背出“索引可以加快查询”,但当被问到“为什么这个索引没被用上”或者“为什么索引区分度低反而更慢”时,就说不清楚了。本文从面试实战角度出发,把这几个概念串起来讲透。

从一个面试题说起

面试官常问:

有一张用户表,性别字段只有“男/女”两个值,年龄字段有 0-100 左右的值,手机号字段几乎唯一。现在要给这三个字段建索引,你会怎么建?为什么?

标准答案不是“都建”,也不是“只建手机号”,而是需要理解基数选择性之后做出的判断。

什么是索引基数(Cardinality)

索引基数指的是索引列中不重复值的数量。MySQL 在统计信息中会记录每个索引的 Cardinality 值,可以通过以下命令查看:

SHOW INDEX FROM user;

或者更精确地:

ANALYZE TABLE user;
SHOW INDEX FROM user;

输出中的 Cardinality 列就是估算的不重复值数量。

几个关键点:

  • 基数越高,说明列中重复值越少。比如手机号列,基数接近表的总行数。
  • 基数越低,说明列中重复值越多。比如性别列,基数最多是 2。
  • MySQL 的 Cardinality 是估算值,不是精确值。它通过采样统计(默认为 8 个数据页)得出,因此可能不准确。
  • 当表数据变化超过一定比例时,统计信息会过期,需要手动 ANALYZE TABLE 更新。

什么是索引选择性(Selectivity)

选择性是基数的“归一化”版本,计算公式为:

选择性 = 索引基数 / 表总行数

取值范围在 0 到 1 之间:

  • 选择性越接近 1,说明索引列的值越唯一,索引效果越好。
  • 选择性越接近 0,说明索引列重复值越多,索引效果越差。

举例说明,假设表有 100 万行:

字段 基数 选择性 评价
手机号 100万 1.0 极好
年龄 101 0.0001 较差
性别 2 0.000002 极差

一般来说,选择性低于 0.1 的索引,优化器很可能选择全表扫描而不是走索引,因为走索引再回表的代价可能比直接全表扫描还高。

基数与选择性如何影响索引设计

1. 高选择性列优先建索引

这是最基本的原则。手机号、订单号、身份证号这类几乎唯一的列,是索引的最佳候选。它们能让 MySQL 在 B+ 树中快速定位到极少数数据行。

2. 低选择性列单独建索引往往无效

性别列只有两个值,即使建了索引,查询“男”仍然要扫描约一半的数据行。优化器会判断:与其走索引再大量回表,不如直接全表扫描。所以单独为低基数列建索引通常没有意义

3. 联合索引中的顺序问题

联合索引 (a, b, c) 的设计中,选择性高的列应该放在前面吗?答案是:不一定

  • 如果查询条件是等值查询 WHERE a = ? AND b = ?,那么把选择性高的列放前面通常更好,因为能更快缩小扫描范围。
  • 但如果存在范围查询,比如 WHERE a = ? AND b > ?,则应该把等值查询的列放在前面,范围查询的列放在后面。
  • 更重要的原则是最左前缀匹配:联合索引的顺序首先要满足查询模式,其次才考虑选择性。

4. 覆盖索引可以绕过选择性问题

如果一个低选择性列的查询可以通过覆盖索引完成(即索引中已经包含所有需要的列,不需要回表),那么即使选择性低,索引也可能被使用。因为此时没有回表代价,扫描索引比扫描全表更轻量。

例如:

ALTER TABLE user ADD INDEX idx_gender_age (gender, age);
SELECT gender, age FROM user WHERE gender = '男';

这个查询可以直接从索引中获取数据,不需要回表,优化器就更可能选择索引。

面试中的常见追问

追问一:为什么 MySQL 有时明明建了索引却不用?

可能原因包括:选择性太低、统计信息过期、查询条件使用了函数或隐式类型转换、联合索引不满足最左前缀、回表代价评估后不如全表扫描等。

追问二:Cardinality 不准确怎么办?

可以手动执行 ANALYZE TABLE 更新统计信息。在 MySQL 8.0 中,还可以通过 information_schema.STATISTICS 查看更详细的统计。对于大表,可以考虑调整 innodb_stats_persistent 和采样页数参数。

追问三:如何判断一个索引是否值得建?

经验法则是:选择性大于 0.1 且查询频率高的列值得建索引。更严谨的做法是通过 EXPLAIN 对比走索引和全表扫描的实际代价,或者用 optimizer_trace 查看优化器的决策过程。

实战建议总结

  1. 先看选择性,再决定是否建索引。低于 0.1 的列单独建索引要慎重。
  2. 联合索引顺序以查询模式为先,选择性为辅。等值在前,范围在后。
  3. 低选择性列可以通过覆盖索引发挥作用
  4. 定期更新统计信息,避免因 Cardinality 失准导致优化器选错执行计划。
  5. 不要迷信规则,用 EXPLAIN 验证。实际执行计划才是最终答案。

结语

索引基数告诉你“有多少个不同的值”,选择性告诉你“这些值有多分散”,而索引设计则是根据这两个指标加上查询模式做出的综合决策。面试中能把这层逻辑讲清楚,远比背诵“索引能加快查询”更有说服力。理解原理,才能在面对复杂查询时做出正确的索引设计判断。

未经允许不得转载:任鹏个人博客 » MySQL 索引基数、选择性与索引设计的关系

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏