千万级数据的订单表,给订单状态加索引有用吗?在线等!

捡田螺的小男孩 2025-04-27 15:15:50

前言

 

有位读者去快手面试,问了这道题:一个表拥有千万级别数据量,给status字段加索引,是否有效?

 

这道面试题,还是可以从几个维度结合去回答的,我来跟大家聊聊我的看法~~

 

  • 加了索引的简单执行流程

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

  • status区分度与选择行

  • 查询的数据量的影响

  • 结合不同场景的其他字段

  • 分区表

  • EXPLAIN 查询计划

 

一、加了索引的简单执行流程

 

我觉得,在回答这个问题,可以跟面试官说说,普通索引加入后的简单执行流程.

 

假设有一张订单表order_info,然后订单状态字段order_status,有个普通索引idx_order_status,它是B+树索引.

 

我们现在简单点查询,就是查询已完成FINISHED的历史订单:

 

  •  
select * from order_info where order_status = 'FINISHED'

 

我们来说一下这个执行流程:

 

  • 从idx_order_status这棵 B+ 树的根节点开始查找。因为B+ 树是有序的,使用二分查找的方法逐层向下查找,直到找到包含FINISHED的叶子节点。

 

  • 找到FINISHED的起始位置后,数据库会扫描所有包含FINISHED的叶子节点。因为叶子节点存储了主键ID的值.它会遍历索引的叶子节点,以收集所有匹配FINISHED的记录的主键 ID。

 

  • 使用从idx_order_status索引中获取的主键 ID,数据库会回到ID主键索引树,找到对应的行记录(这个过程叫做回表)。

 

二、极端场景,走索引or全表扫描

 

我先给大家讲解一个极端情况: 假设订单表里面,全部记录的订单,它的状态都是FINISHED.

 

那我们的这个查询SQL,它还会先找idx_order_status 索引树,找到FINISHED的叶子节点,然后找到主键id后,再回表,找到对应的记录吗?

 

那肯定不会呀,我们都知道MySQL有优化器的,它发现这一波操作下来(又B+索引树搜索,又回表,多次IO),还没全表扫描快,那就肯定不走索引啦,而是选择全表扫描啦.

 

 

这个极端情况,大家应该能理解吧? 那我们再往下一步,假设一千万数据的表,有那么的几条数据,它的订单状是已下单.其他状态都是已完成(FINISHED),那么你觉得会走索引嘛?

 

那也不会走索引,因为mysql执行查询的时候,假设走索引,还是那一波操作(又B+索引树搜索,又回表,多次IO), 只有几条其他状态的记录,最后时间肯定也是没有直接全表扫描快的.因此还是会全表扫描.

 

三、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 很低,说明创建的普通索引对性能提升不大。

 

 

四、查询的数据量的影响

 

如果查询返回的数据量很大,比如查询状态为 "已完成" 的所有订单,即使有索引,数据库可能也会倾向于全表扫描,因为回表操作(即通过索引找到记录后再根据主键去查找完整行数据)可能比全表扫描还要慢。

 

这时候,我们可以做一些优化,比如,使用覆盖索引

 

即将查询的字段都包含在索引中,避免回表。例如,如果你的查询只需要 order_status 和 order_id,那么可以为 order_status 和 order_id 创建联合索引,这样索引中就可以直接返回结果,而不需要回表查询数据。

 

有些时候,比如你查询已完成的订单,如果返回的数据量特别多,一般要求用分页的,这时候,基于分页,可以针对一些做深分页优化,比如使用标签记录法.

 

 

五、结合不同场景的其他字段

 

单纯查已完成订单的话,业务场景是比较少的. 一般结合其他场景来一起使用.

 

如果是结合其他场景,那加联合索引,效果就会比较明显啦.

 

  • 比如查询某个客户的已完成订单,就是说结合客户号来一起查询,这时候加联合索引 idx_client_no_order_status查询效果就很明显.

 

  • 又或者查询最近三个月的已完成订单,就是说结合订单创建时间来查询.加联合索引idx_create_time_order_status查询效果也很不错

 

六、分区表

 

如果有些时候,你的查询只是根据订单条件来查,不结合其他场景的字段. 那还是可以做一些优化,比如分区表.

 

如果订单表的数据量很大(如上千万级别),考虑使用分区表,可以基于 order_status 字段或其他高选择性的字段进行分区。分区表可以将数据按指定规则分布到多个物理存储区域中,查询时只需要扫描部分分区,提高查询效率。

 

七、EXPLAIN 查询计划

 

我们写完查询SQL的时候,经常建议用EXPLAIN 查看一下查询计划.通过 EXPLAIN 查看查询计划,判断是否使用了索引,以及该索引是否在查询中实际被用到。

 

例如:

 

  •  
EXPLAINSELECT * FROM order_info WHERE order_status = 'FINISHED';

 

看看是否返回了 index 或 ref 这样的结果。如果查询结果中没有显示索引使用(如 type 为 ALL,表示全表扫描),说明当前的索引并没有起作用。

 

作者丨捡田螺的小男孩
来源丨公众号:捡田螺的小男孩(ID:gh_3d11c9893ca0)
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告