MySQL 索引的最左前缀匹配原则是什么?
MySQL 索引的最左前缀匹配原则是什么?
回答重点
MySQL 索引的最左前缀匹配原则指的是在使用联合索引时,查询条件必须从索引的最左侧开始匹配。如果一个联合索引包含多个列,查询条件必须包含第一个列的条件,然后是第二个列,以此类推。
底层原理:因为联合索引在 B+ 树中的排列方式遵循“从左到右”的顺序,例如联合索引 (first_name, last_name, age) 会按照 (first_name, last_name, age) 的顺序在 B+ 树中进行排序。
MySQL 在查找时会优先使用 first_name 作为匹配依据,然后依次使用 last_name 和 age。因此,组合索引能够从左到右依次高效匹配,跳过最左侧字段会导致无法利用该索引。
按照 (first_name, last_name, age) 的顺序在 B+ 树中的排列方式(大致的示意图)如下
1 | (Alice, Black, 35) |
扩展知识
举例说明
假设当前表有一个联合索引(a,b,c)。
以下查询条件 符合 最左匹配原则:
1 | where a=1; |
以下查询条件 不符合 最左匹配原则:
1 | where b=2; |
再来看下这个查询:
1 | where a=1, c=3; |
这个查询可能可以用上联合索引(a,b,c),在 5.6版本之前,即使用上也只能利用 a=1 这个条件来过滤。但是在 MySQL 5.6 版本后有个优化,即索引下推,可以在引擎层面,查询得到 a=1 的数据之后,利用 c 过滤掉不符合的数据,再返回给 server 层。
注意范围查询
在理解范围查询不同点之前,需要理解联合索引的顺序。
例如联合索引(a,b,c),在 B+树的中排序是:先按 a 的值排序,如果 a 的值相同,则按 b 的值排序,如果 b 的值相同,再按 c 的值排序。
类似下图的排序:
如果遇到范围查询(>、<),就会停止匹配。
1 | where a>1, b=2, c=3; |
所谓的停止匹配指的是:a 可以用上联合索引,但是 b 和 c 却不行。因为 a 需要先经过范围查询,此时经过筛选得到 b 和 c 的数据是无序的。比如 a 为 1 和 a 为 2 数据之间 b 和 c 是无序的,因此无法利用索引查询。
如果遇到如 >=、<=、BETWEEN、前缀like(xx%)的范围查询,则不会停止匹配。因为这些查询包含一个等值判断,可以直接定位到某个数据,然后往后扫描即可。
例如:
1 | where a>=1, b=2, c=3; |
查询可以定位到 a=1 这条数据,然后往后遍历即可,而定位到的 a=1 数据内,b 和 c 是有序的,因此可以利用索引查询。
而以下的查询则可以全部用上联合索引:
1 | where a=1, b=2, c>3; |
因为 a 和 b 是等值查询的情况下,c 是有序的,有序就可以用上索引。
补充说明:拿
where a>=1, b=2,再来理解一下。因为 a = 1 这个条件,再加上 b = 2,mysql 可以直接定位到 a=1 and b=2 这条数据,然后再往后遍历,因此说用上了 b(因为确实利用了 b 的条件快速定位到第一条记录)。但是要注意往后遍历即 扫描 a > 1 的情况是用不到 b 的。
MySQL 8 都不需要最左匹配就能用上联合索引了?
以下实验基于 MySQL8.0.26 版本
为了故事顺利的发展,我们先建个表。
1 | CREATE TABLE `t1` ( |
很简单的一张表,一个主键索引,一个联合索引idx_f2_f3。
此时插入一波数据
1 | INSERT INTO t1 (f2,f3) VALUES |
这个时候表里一共有160条数据,f2 的值基数很低,只有1、2这两个值,基数为2。
数据准备完了,咱们可以开始操作一下了,此时执行:
先执行了一下ANALYZE TABLE t1; 更新了一下表的统计信息,防止 MySQL 误判。
简单执行了一个 f2 为条件的联合索引查询,且结果只要求返回 f2 和 f3,所以从这个联合索引可以直接得到值,不需要回表。
这个查询的 explain 结果相信大家也都能理解,用上了 idx_f2_f3,且 type 是ref,说明用上了这个索引查询。
现在我们换一个查询条件,让 f3 作为条件来查询,这个时候就不是最左匹配原则了,讲道理应该用不上索引查询。
看看这结果,好像不太对?type 是 range,这是基于索引的范围扫描,所以用上了索引!
但是我的查询条件 f3=1 明明是等值呀?怎么就用上了范围扫描呢?
还有 Extra 里面显示的Using index for skip scan又是啥?
Using index for skip scan
让我们直接进行一个官方文档的查询!
在优化里面的范围优化里确实有Skip Scan Range Access Method,它就是产生上述 Extra 的原因。
好了,可以忘了官网这个截图了,基于我展示的例子我来解释下。
根据我们所认知的最左匹配原则,执行
- where f3 =1
是无法在idx_f2_f3上利用 type 是 ref 的查询方式来查询的。
因为只有带上索引左边的查询条件才能用上索引,例如:
- where f2=1 and f3 =1
那如果无法利用索引进行查询,最终只有一个选择,全表扫描:要么全表扫描主键索引,要么全表扫描二级索引。
MySQL 就意识到这效率确实低,能不能找个优化点呢?
所以就在 8.0.13 版本引入了Skip Scan Range Access Method,它在一定条件下,利用了范围扫描来替代了全表扫描的发生。
具体原理如下:
- 先统计一下索引最左字段 f2 的基数即唯一值,这里一共有 1 和 2 两个值。
- 然后将 f2 的值拼入查询条件中构造
where f2=1 and f3=1和where f2=2 and f3=1这样的条件进行查询。
这波是不是小秀?
你 SQL 不带索引最左的条件是吧,我自个儿给你拼上去!
所以一条查询就变成了多次查询,所以 type 就变成了 range 了。
因此最终还是没有逃出最左匹配原则,只是 MySQL 隐式的构造了查询条件,使得看起来好像不需要最左匹配原则而已。
这个优化的中文名叫:跳过扫描范围访问(我直译的,哈哈哈)
跳过扫描范围访问的局限性
不要高兴的太早,这个优化其实有很大的局限性!
它要求最左条件的基数很低,因为它需要列举最左条件的值来构造查询,假如最左条件有上万个,那还不如直接进行全表扫描了对吧。
其实也不需要上万个,我来改一下表里的数据来做一次实验。
我直接把 f2 的值随机化了,这样 f2 的基数就变大了,可以看到此时再执行查询语句就用不上了Using index for skip scan了。
变成了一个基于二级索引的全表扫描。
除了上面这个关键点之外,还有很多局限性,我根据官网的内容做了一下翻译(和一点便于理解的小修改):
- 需要联合索引
- 查询不能跨表
- 查询不能使用 GROUP BY 或者 DISTINCT
- 查询只能用一个索引,即索引需要覆盖查询的值,不能回表
- 查询条件必须是常量,包括 IN() 运算符。
还有两条我不翻译了,我觉得讲的是废话…有点兴趣的自己去看看吧,文末会放链接。
总而言之,能用上这个优化的场景很有限,就基数很低这一个限制我就觉得很大了。
所以这也不是什么万能药,就当一个知识点咱们知晓下,到时候遇到特殊场景能用上的时候装一下,或者在面试官问你最左匹配的时候,提一下,我知道那个 MySQL 8.0.13 版本啊,它做了个优化…..