实际案例—索引区分度

如果运行中数据量不多,大多数都是历史数据,那么加一下来区分运行中数据查询统计还是可以的,当然有用有redis来做统计的更加优雅的方式
 
一个表拥有千万级别数据量,给status字段加索引,是否有效?
这道面试题,还是可以从几个维度结合去回答的,我来跟大家聊聊我的看法~~
  • 加了索引的简单执行流程
  • 极端场景,走索引or全表扫描
  • status区分度与选择行
  • 查询的数据量的影响
  • 结合不同场景的其他字段
  • 分区表
  • EXPLAIN 查询计划

1. 加了索引的简单执行流程

我觉得,在回答这个问题,可以跟面试官说说,普通索引加入后的简单执行流程.
假设有一张订单表order_info,然后订单状态字段order_status,有个普通索引idx_order_status,它是B+树索引.
我们现在简单点查询,就是查询已完成FINISHED的历史订单:
我们来说一下这个执行流程:
  • idx_order_status这棵 B+ 树的根节点开始查找。因为B+ 树是有序的,使用二分查找的方法逐层向下查找,直到找到包含FINISHED的叶子节点。
  • 找到FINISHED的起始位置后,数据库会扫描所有包含FINISHED的叶子节点。因为叶子节点存储了主键ID的值.它会遍历索引的叶子节点,以收集所有匹配FINISHED的记录的主键 ID。
  • 使用从idx_order_status索引中获取的主键 ID,数据库会回到ID主键索引树,找到对应的行记录(这个过程叫做回表)。

2. 极端场景,走索引or全表扫描

我先给大家讲解一个极端情况: 假设订单表里面,全部记录的订单,它的状态都是FINISHED.
那我们的这个查询SQL,它还会先找idx_order_status 索引树,找到FINISHED的叶子节点,然后找到主键id后,再回表,找到对应的记录吗?
那肯定不会呀,我们都知道MySQL有优化器的,它发现这一波操作下来(又B+索引树搜索,又回表,多次IO),还没全表扫描快,那就肯定不走索引啦,而是选择全表扫描啦.
这个极端情况,大家应该能理解吧? 那我们再往下一步,假设一千万数据的表,有那么的几条数据,它的订单状是已下单.其他状态都是已完成(FINISHED),那么你觉得会走索引嘛?
那也不会走索引,因为mysql执行查询的时候,假设走索引,还是那一波操作(又B+索引树搜索,又回表,多次IO), 只有几条其他状态的记录,最后时间肯定也是没有直接全表扫描快的.因此还是会全表扫描.

3. order_status 区分度与选择性

对于这道面试题,我的星球粉丝群有讨论过,我摘抄来一些讨论点下来,给大家看看:
如果orderStatus字段的值分布很广,那么索引会更有效。如果大多数行都有相同的状态值,索引效果可能不佳数据倾斜严重:如果status字段的值分布极不均匀,大部分数据集中在少数几个状态上,索引的选择性就会降低,查询效率提升有限首先增加索引肯定会需要额外的空间去储存,另外会影响增删改的性能 其次离散性低代表通过索引筛选出的数据量较多,例如status三种状态,1/3的数据和整体数据提升效果较小 然后通过二级索引查询数据,是先通过二级索引查询id,然后在通过id去聚簇索引查找数据,多增加一次io消耗
这些观点,其实都跟区分度和选择性有关.比如,订单表有好多种状态,每种状态的数据量都比较均衡,也就是说订单各种状态区分度很好. 再换种专业说法吧,选择性
选择性(Cardinality) 是指列中不同值的数量与总记录数的比例。简单来说,选择性越高(即字段的不同值越多、重复率越低),索引的效果就越好。
  • 如果 status 字段的值非常少且重复率高(例如只有 "下单"、"已支付"、"已取消" 这几种状态),那么普通索引可能不会显著提升查询效率,因为数据库可能会选择进行全表扫描。这种情况下,索引的选择性太低,查询时即便走索引,命中的行数也会很多,反而可能导致查询性能变差。
那我们怎么判断,这个选择性呢? 有个方法
判断方法:可以通过 MySQL 的 SHOW INDEX 命令查看索引的 Cardinality 值,它表示索引的选择性。值越大,索引越有效。sql SHOW INDEX FROM order_info WHERE Key_name = 'idx_order_status'; 如果 Cardinality 很低,说明创建的普通索引对性能提升不大。

4. 查询的数据量的影响

如果查询返回的数据量很大,比如查询状态为 "已完成" 的所有订单,即使有索引,数据库可能也会倾向于全表扫描,因为回表操作(即通过索引找到记录后再根据主键去查找完整行数据)可能比全表扫描还要慢。
这时候,我们可以做一些优化,比如,使用覆盖索引
即将查询的字段都包含在索引中,避免回表。例如,如果你的查询只需要 order_status 和 order_id,那么可以为 order_status 和 order_id 创建联合索引,这样索引中就可以直接返回结果,而不需要回表查询数据。
有些时候,比如你查询已完成的订单,如果返回的数据量特别多,一般要求用分页的,这时候,基于分页,可以针对一些做深分页优化,比如使用标签记录法.

5. 结合不同场景的其他字段

单纯查已完成订单的话,业务场景是比较少的. 一般结合其他场景来一起使用.
如果是结合其他场景,那加联合索引,效果就会比较明显啦.
  • 比如查询某个客户的已完成订单,就是说结合客户号来一起查询,这时候加联合索引 idx_client_no_order_status查询效果就很明显.
  • 又或者查询最近三个月的已完成订单,就是说结合订单创建时间来查询.加联合索引idx_create_time_order_status查询效果也很不错

6. 分区表

如果有些时候,你的查询只是根据订单条件来查,不结合其他场景的字段. 那还是可以做一些优化,比如分区表.
如果订单表的数据量很大(如上千万级别),考虑使用分区表,可以基于 order_status 字段或其他高选择性的字段进行分区。分区表可以将数据按指定规则分布到多个物理存储区域中,查询时只需要扫描部分分区,提高查询效率。

7. EXPLAIN 查询计划

我们写完查询SQL的时候,经常建议用EXPLAIN 查看一下查询计划.通过 EXPLAIN 查看查询计划,判断是否使用了索引,以及该索引是否在查询中实际被用到。
例如:
EXPLAIN SELECT * FROM order_info WHERE order_status = 'FINISHED';
看看是否返回了 index 或 ref 这样的结果。如果查询结果中没有显示索引使用(如 type 为 ALL,表示全表扫描),说明当前的索引并没有起作用。
Loading...
目录
文章列表
王小扬博客
产品
Think
Git
软件开发
计算机网络
CI
DB
设计
缓存
Docker
Node
操作系统
Java
大前端
Nestjs
其他
PHP