MySQL 索引的 “最左前缀原则”,用查字典的例子讲透

2025-12-12 0 176

前言

前两天和实习生聊天,他问我为啥MySQL的索引要讲究最左前缀

我当时没直接回答,想着找个日常的例子才好说,不然光讲概念应该比较难理解。继而转念想到——这不就跟咱们小时候查新华字典一个道理嘛!

Oh Hell No Pokemon GIF.gif

正文

先说查字典的例子

小学的时候老师教我们查新华字典,你想查一个字,得先找偏旁部首,再数笔画

假如你想查“赵钱孙李”的赵字,你会怎么查?

肯定是先找走字旁,然后在走字旁里面数笔画。你不可能说,我就看这个字右边那部分,然后跳过偏旁直接去找,那肯定是行不通的,不合常理。

MySQL的索引就是这个意思。

再拿身份证号码举例

咱们国内的身份证号码,前六位是地区编码。比如110101开头的,一看就知道是北京东城区的。

假设现在有个用户表,建了个联合索引:

INDEX idx_area_birth (province, city, birthday)

这个索引有三个字段:省份、城市、生日。就像身份证号码的结构一样,从大到小排列。

能用上索引的查询

你要查北京朝阳区1990年出生的人:

WHERE province = \'北京\' 
  AND city = \'朝阳\' 
  AND birthday = \'1990-01-01\'

这个必然没问题,就像你翻通讯录,先找北京,再找朝阳区,最后找1990年的。一路定位下来,肯定是顺利的。

或者你只查北京的:

WHERE province = \'北京\'

这样也行,就找第一层,找到就算,后面不管了。

用不上索引的查询

但要是这么查:

WHERE city = \'朝阳\'

跳过省份直接查城市?就不好弄了。

就好比你拿着通讯录,不看省份,直接找朝阳区。全国好几个地方都有朝阳区呢,北京有朝阳区,长春也有朝阳区,非得把整本通讯录都翻一遍才行。

这就是为什么要最左前缀。

索引是按顺序建的,你得从最左边开始匹配。

graph TD
    A[联合索引树根节点] --> B[北京]
    A --> C[上海]
    A --> D[广东]
    B --> B1[朝阳]
    B --> B2[海淀]
    B --> B3[东城]
    B1 --> B1a[1990-01-01]
    B1 --> B1b[1991-05-20]
    B1 --> B1c[1992-08-15]
    
    style B1a fill:#90EE90
    style B1b fill:#90EE90
    style B1c fill:#90EE90

看这个树状结构,也比较清晰。

要找数据,必须从根往下走,一旦跳过北京这一层,就不知道该往哪个分支走了。

B+树的结构

MySQL的索引其实用的是B+树,这玩意儿可以简单理解成一个多叉排序树。

就像咱们前面说的通讯录,整个通讯录的目录就是根节点,先按省份分成几大类(北京、上海、广东这些)。每个省份下面又按城市分小类(北京下面有朝阳、海淀、东城),每个城市下面再按生日排列具体的人。

graph TD
    Root[索引根节点
指向不同范围] Root --> L1[范围1
province: 北京] Root --> L2[范围2
province: 上海] Root --> L3[范围3
province: 广东] L1 --> L1_1[北京+朝阳
指向数据] L1 --> L1_2[北京+海淀
指向数据] L2 --> L2_1[上海+浦东
指向数据] L2 --> L2_2[上海+徐汇
指向数据] L3 --> L3_1[广东+深圳
指向数据] L3 --> L3_2[广东+广州
指向数据] L1_1 --> Data1[实际数据记录] L1_2 --> Data2[实际数据记录] L2_1 --> Data3[实际数据记录] style Root fill:#FFB6C1 style L1 fill:#87CEEB style L2 fill:#87CEEB style L3 fill:#87CEEB style Data1 fill:#90EE90 style Data2 fill:#90EE90 style Data3 fill:#90EE90

B+树就是这样的结构,如果跳过第一层(省份)直接查第二层(城市),数据库就不知道该往哪个分支走。它只能把所有分支都走一遍,那还要索引干嘛呢?

为什么要这样设计?

其实仔细想一下,挺简单的。

你在超市买东西,商品分类摆放。日用品在左边,食品在右边,生鲜在最里面。你要买个牙膏,直接去左边日用品区找就行,不会说让你在整个超市乱转悠。

数据库索引也是为了快速定位数据。

如果每次查询都要扫描全表,那数据库性能得有多差啊。

建立索引的时候,数据库会把数据按照索引字段的顺序排好。就像图书管理员会把书按分类、按作者、按书名整理好,这样查的时候才能快。

但这个排序也是有顺序的,你不能指望一堆书既按作者排序,又同时按书名排序。它必须先按一个维度排,再按另一个维度排。

实际工作中怎么用?

比方说现在有个订单表,建了个索引(user_id, order_time, status),用户ID、下单时间、订单状态。

运营那边提了个需求,说要查某个时间段内所有已完成的订单。然后写了个SQL:

SELECT * FROM orders 
WHERE order_time BETWEEN \'2024-01-01\' AND \'2024-12-31\'
  AND status = \'已完成\'

结果这个查询慢得要死。为啥?因为它跳过了user_id这个字段,直接用order_time查,索引其实完全没有用上。

于是再加个索引(order_time, status),问题得以解决。

所以建索引的时候,务必得想清楚业务上的查询场景,最忌讳闭门造车。

那问题就来了,建索引的时候,怎么决定字段的顺序呢?

这个要看你的业务场景。一般来说,把最常用的查询条件放最左边。如果某个字段几乎每次查询都会用到,那就把它放第一位。

还有就是区分度。假如某个字段只有几个固定值,比如性别只有男女,这种字段的区分度就很低,放在索引前面意义不大。

像用户ID、订单号这种,每条记录都不一样,区分度很高,适合放前面。

写在最后

说到底,还是得看具体的业务场景,理论归理论,实践起来还真说不好。

最好的办法就是建完索引后,先用 EXPLAIN 看看执行计划。 如果没用上你想要的索引,那就再改、再测。

我想这是一个程序员面对现实的必修课——在不断地试错中,接受自己的平庸,并为那一点点性能提升而努力。

Pokemon Fighting GIF.gif

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 MySQL 索引的 “最左前缀原则”,用查字典的例子讲透 https://www.zuozi.net/35747.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务