mysql为什么不推荐在低区分度列加索引_深入B+树节点查找原理

梦宇吖_3286

梦宇吖_3286

2026-05-01

706人浏览

原创

根本原因是低区分度列导致b+树退化为宽浅结构,索引查找变成顺序扫描大量重复值,回表开销大且无法跳过无效数据;cardinality为采样估算值,易失真,真实区分度低于5%时优化器通常弃用索引。

mysql为什么不推荐在低区分度列加索引_深入b+树节点查找原理

MySQL 在低区分度列(比如 status 只有 0/1,gender 只有 'M'/'F')上建索引,不仅不加速查询,反而可能拖慢整体性能——根本原因不在“能不能用”,而在 B+Tree 节点查找时的磁盘 IO 效率崩塌。

低区分度列导致 B+Tree 查找退化为“顺序扫描”

联合索引或单列索引本质是 B+Tree,每个叶子节点存的是「索引值 + 主键」。当字段区分度极低(例如 Cardinality 接近 1),大量行共享同一个索引值,B+Tree 就会变成“宽而浅”的结构:根节点下直接挂几十万条相同 status=1 的记录指针。

此时 MySQL 仍要从根开始查,但真正耗时的不是树高,而是:一旦定位到该索引值对应的第一条记录,就必须顺着叶子节点链表逐条比对其他 WHERE 条件(如 user_id = 12345),因为 B+Tree 并不保证这些记录在物理上连续或有序——它只按索引列排序,不按其他列排。

  • 实际效果 ≈ 先扫出 80% 的行,再在内存里过滤剩下条件
  • 如果该索引还被用于 ORDER BY status,排序毫无意义;用于 GROUP BY status,分组也几乎无压缩
  • EXPLAIN 中 rows 值虚高,key_len 小,但 Extra 出现 Using filesort 或 Using where 频繁

为什么 SHOW INDEX FROM table 的 Cardinality 不可靠?

Cardinality 是采样估算值,不是精确统计。InnoDB 默认只采样 16 个叶子节点页来推算整棵树的去重数,当表数据倾斜严重(比如 95% 的 status=1,5% 的 status=0),采样结果可能完全失真。

MySQL
MySQL

编写正确的MySQL查询,避免字符集、索引和锁方面的常见陷阱。

下载

更麻烦的是:这个值不会自动刷新。即使你大批量更新了 status,Cardinality 仍可能停留在旧值,导致优化器误判索引有效,继续走错执行计划。

  • 手动触发更新:ANALYZE TABLE your_table;
  • 但即便更新后,若真实区分度仍低于 5%,MySQL 通常仍会放弃使用该索引(尤其配合范围查询时)
  • 验证方式不是看 Cardinality 绝对值,而是执行 SELECT COUNT(DISTINCT status) / COUNT(*) FROM your_table; 算真实区分度

哪些场景下低区分度列“意外有用”?

不是绝对不能建,而是必须满足特定前提——否则就是给优化器埋雷。

  • 该列是联合索引的**最左前缀**,且后续列区分度足够高(如 (status, create_time, user_id),其中 create_time 是时间戳、user_id 是主键)
  • 查询中同时存在该列的等值条件和后续列的范围条件(如 WHERE status = 1 AND create_time > '2025-01-01'),这时 B+Tree 能先切出 status=1 子树,再在子树内高效走范围
  • 该列被用于覆盖索引(SELECT status FROM t WHERE status = 1),无需回表,IO 完全落在索引页内
  • 注意:如果写成 WHERE status IN (0,1),哪怕只有两个值,也会让索引失效——IN 列表过长或含多个低区分值,优化器大概率转为全表扫描

B+Tree 不是魔法盒,它只忠实地按索引定义排序并组织数据。低区分度列的问题不在“建不建”,而在“建了之后,MySQL 还有没有机会跳过无效数据”。多数时候,它只是把随机 IO 换成了更密集的顺序 IO,而且回表成本更高——这点很容易被 EXPLAIN 的 type=ref 假象掩盖。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

2023.10.12

4443

8

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.27

931

4

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

2024.02.23

1169

5

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

6381

10

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

3183

4

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

2024.04.07

6360

11

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

2024.04.29

8661

6

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1170

5

sql中删除一列的命令是什么
sql中删除一列的命令是什么

在sql中,使用alter table语句可以删除一列,语法为:alter table table_name drop column column_name。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1032

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 189人学习