ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

索引建了一堆,查询为什么还是慢?——联合索引与最左前缀原则,一次讲透

索引建了一堆,查询为什么还是慢?——联合索引与最左前缀原则,一次讲透 你给订单表建了个联合索引(user_id, create_time, status)信心满满。结果上线后一条WHERE create_time 2026-09-01 AND status 1的查询还是把整张表扫了一遍。DBA 瞟了一眼你的 SQL甩过来四个字“最左前缀。”你一脸懵索引我建了呀三列都在里面呀凭什么不走问题就出在——你以为建了联合索引 三列都能用可实际上联合索引是一根有顺序的链条它只对从最左边开始的查询有用。这一篇把这件事从头到尾掰开。索引凭什么快因为它有序先回到最底层的那个直觉我之前在《数据库索引为什么用 B 树》里聊过 B 树的结构这里只讲结论索引的本质是一份排好序的目录。一本字典你能秒查苹果这个词是因为所有词条按拼音排了序。排序让你能对半切每次排除一半而不是一页一页翻。单列索引排序很好理解age列建索引就是把所有行的age从小到大排好。查age 18顺着排好序的链表/树几下就定位到了。可联合索引呢多列怎么排成一列答案是按字典序一列一列地排。联合索引(a, b, c)的排序规则是先按a排序a相同的行再按b排序a、b都相同的行再按c排序。说白了就是把(a, b, c)当成一个三位的号码逐位比较。这跟你手机通讯录先按姓氏、再按名字排是一模一样的道理。关键来了第二列只在第一列相同时才有序这是理解最左前缀的唯一钥匙请盯住这句话在联合索引里b列不是全局有序的它只在a相同的那一小段里才有序。举个例子联合索引(a, b)排出来的顺序长这样(a, b) (1, 2) (1, 5) (1, 9) ← 在 a1 这一段里b 是 2、5、9有序 (2, 1) (2, 3) (2, 8) ← 在 a2 这一段里b 是 1、3、8有序 (3, 4) (3, 6) ← 在 a3 这一段里b 是 4、6有序现在你问它b 5在哪你顺着整个索引扫一遍b列2, 5, 9, 1, 3, 8, 4, 6——这是乱的因为每个a值都会让b重新从它自己的最小值开始排。既然b这一列整体是乱的索引就没法用有序这个本事去对半查找b只能从头扫到尾。这就是b 5用不上(a, b)索引的底层原因。反过来a 2 AND b 3就能用先靠a的有序定位到a2那一段再靠这一段里b的有序定位到b3。两步都踩在有序上。于是最左前缀原则就出来了把上面的逻辑翻译成人话就是那条著名的规则联合索引(a, b, c)等价于三个缩小版索引(a)、(a, b)、(a, b, c)。但凡是没从a开始的查询都用不上它。对照着看这几条 SQL 的命中情况一目了然SQL 条件命中索引的列为什么WHERE a 1 AND b 2 AND c 3a、b、c 全命中从最左开始逐列有序WHERE a 1 AND c 3只有 a中间跳过了 bc 在a1 且 b 不定的前提下是乱的WHERE b 2一列都没有没从最左列 a 开始WHERE a 1 AND b 2只有 a范围a 一旦是范围b 就断档了最后一行最值得细说因为它最反直觉。为什么范围查询会让后面的列断档WHERE a 1是什么是一段区间不是一个点。定位到a 1后命中的是所有a 2、3、4……的行。这些行里b的取值是在 a2 里排一遍、再在 a3 里排一遍、再在 a4 里排一遍……——每一段内部有序段与段之间又是乱的。所以a 1 AND b 2索引只能帮你锁定a 1 那一大段b 2这个条件没法利用有序性只能在那一段里逐行过滤。这就是为什么老 DBA 会念叨范围查询要放在联合索引的最后一列。如果你经常按(a 某个值, b 范围)查就该建(a, b)如果还经常(a, b 范围, c 等值)查那c注定吃不到索引得另外想办法。一句话记住索引是字典你得从第一页开始查你可以把联合索引想象成一本多级目录的电话簿先按省分再按市分再按姓名分。你说查江苏省的人——能用第一列你说查江苏省南京市的人——能用第一、二列你说查所有姓王的人——用不了因为姓王的人散落在每个省、每个市里目录根本没按姓整体排过序。最左前缀说的就是你要用这本目录就必须从省这一级开始往下查不能跳级也不能从中间某一级开始。光知道为什么失效还不够再补两刀第一刀列上套函数/运算索引立刻失效。比如WHERE YEAR(create_time) 2026索引里存的是原始create_time你给它套了个YEAR()排序的钥匙就变了索引不认识只能全表扫。改成WHERE create_time 2026-01-01 AND create_time 2027-01-01就又能用上了。同理WHERE age 1 20要改成WHERE age 19。第二刀隐式类型转换是隐蔽的索引杀手。phone是字符串列你写WHERE phone 13800138000数字数据库得把每一行的phone都转成数字再比较这一转索引就废了。写WHERE phone 13800138000带引号才对。这些失效场景本质和最左前缀是同一件事索引是一份按特定规则排好序的目录一旦你的查询条件破坏了那个排序规则跳列、套函数、改类型目录就没法用了。顺手把回表和覆盖索引也说清有了最左前缀的基础这两个八股概念就顺理成章了。联合索引二级索引的叶子节点里存的是索引列 主键值不是整行数据。所以你查SELECT * FROM t WHERE a 1先靠索引找到主键还得再回主键索引聚簇索引查一遍才能拿到整行——这叫回表多跑一趟你查SELECT a, b FROM t WHERE a 1要的列a、b索引里全有不用回表直接返回——这叫覆盖索引。所以面试官问为什么要避免SELECT *一半是为了省网络一半就是为了能走覆盖索引、少回表。这里面的链条全是 B 树结构推出来的不是死记的八股。结语先把为什么再背是什么最左前缀原则背起来就一句话但真正理解它靠的是往下多想一层——索引快的本质是有序联合索引的有序是逐列嵌套的所以只有从最左开始、逐列对齐的查询才踩得上有序性。下次再看到索引失效别急着翻八股清单先问自己一句这条查询还能不能用上有序从哪一列开始有序就断了想清楚这个你甚至不用背规则自己就能推出来。数据库索引、查找、排序这些底层都长在数据结构这门课上。想把这些串成体系、把 408 的查找章节吃透推荐 B站【408实验室】的《数据结构》系统跟学配着 CoLearnyantucs.com的 AI 答疑和错题集边学边问事半功倍。
返回列表