联合索引最左匹配原则,在遇到范围查找(如 >、<)的时候,就会停止匹配。也就是范围查找的字段可以用到联合索引,但是在范围查询字段后面的字段无法用到联合索引。对于 >=, <=, between and, like 前缀匹配 这四种范围查询,并不会停止匹配。
对于以上找个结论,做几个实验来验证一下。
B+Tree 索引
先介绍一下MySQL的B+tree 索引算法
MySQL的innodb存储引擎的索引采用B+Tree的算法
为了便于理解B+Tree索引的存储和查询过程,通过一个简单的例子,了解一下B+Tree索引的具体实现。
假设一张表,有如下数据
id 为主键索引
p_no 为二级索引
这张表中的索引是如何存储的?
主键索引
B+Tree 是一种多叉树,主键索引的叶子节点存在整行数据,非叶子节点只存在索引和指针。主键索引的叶子节点中的数据是按照主键的顺序存储的,因此,主键索引又称之为聚簇索引。同时,每一个叶子节点都指向下一个叶子节点,形成一个链表,便于范围查询。
主键索引的B+Tree存储结构图
假设,执行 select * from t where id =5 这样一条语句,该查询语句的条件是找到id=5的这条记录。因为 B+Tree 是一个有序的数据结构,所以可以通过二分查找算法快速定位这条记录,也就是常用的索引查询,具体过程如下:
从根节点开始,将5 与根节点的索引数据(1,10,20) 做对比,5在 1和10 之间,根据二分查找算法,通过指针找到第二层的索引数据(1,4,7)
在第二层的索引值(1,4,7)中查找,5 在 4和7之间,通过指针找到第三层叶子节点的的索引数据(4,5,6)
在叶子节点的索引数据(4,5,6)中找到 id=5的数据。
二级索引
对于非主键字段建立索引,称之为二级索引。二级索引在叶子节点存储的不是整行数据,而是二级索引的索引值和主键的键值
在t表中建立 p_no字段为二级索引,同样是采用B+Tree索引,二级索引的存储结构图如下:
二级索引树是按照二级索引顺序存放的,如果二级索引值相同再按照主键索引排序。
如果按照二级索引字段 p_no查询
select * from t where p_no =1002;
查询过程会首先在二级索引中根据二分查找算法找到 p_no=1002的二级索引记录,由于查询语句中是 "select *" 还需要查询索引值以外的其他字段的数据,因此,会再根据主键索引值到主键索引树中查找对应的叶子节点上的数据。这个过程叫做回表,也就是说找个查询操作需要访问两个索引树才能查到数据。
如果查询的数据在二级索引的叶子节点能查询到,就不需要再到主键索引去查找了。例如:
select id from t where p_no =1002;
这种不需要回到主键索引中查找数据的叫做 覆盖索引,之查找二级索引就能满足需求。
联合索引
建立(p_no,name)的联合索引,联合索引的非叶子节点用两个字段作为索引值
联合索引的索引树,先按照p_no进行排序,然后在p_no相同的情况下再按照name的顺序进行排序。
最左匹配原则
联合索引存在最左匹配原则,如果不遵循最左匹配原则,联合索引会失效。
例如,创建了一个(a,b,c)三个字段的联合索引,可以用到索引的情况有以下三种
where a =1;
where a =1 and b=2;
where a=1 and b=2 and c=3;
总结一句话为:带头大哥不能少,中间兄弟不能断!
因为有优化器的存在,所以a字段的顺序不重要,但是如果出现以下几种情况,就是不符合最左匹配原则,将会导致索引失效
b=2;
c=3;
b=2 and c=3;
上面这些查询条件之所以会导致索引失效,是因为(a,b,c)的联合索引,索引在存储时先按照a的顺序存储,在a字段相同时再按照b字段的顺序存储,在b字段相同时再按照c字段的顺序存储。所以,b 和 c 字段时全局无序,局部相对有序。所以在没有a字段的前提下无法使用索引。
假设表中数据如下,有(a,b)的联合索引。
该联合索引的 B+Tree结构如图
可以看出 a是全局有序的(1,2,2,3,4,5,6,7),而b是全局无序的(10,4,5,8,9,2,1,6)。因此,直接执行where b=2 是无法使用联合索引的,能够使用索引的前提是索引里的key的有序的。
只有在 a 相同的情况下,b才是有序的,当a=2 时,b的值为 (4,5),这时候b是有序的,找个有序状态是局部的。因此只有在 a=2 and b=4 这样的查询条件下,才能使联合索引生效。
联合索引的范围查询
联合索引中存在一些特殊情况,会导致联合索引中的部分字段的索引失效。也就是说查询过程中使用到了联合索引,部分字段用到了联合索引,部分字段没有用到联合索引。
这种特殊情况就是指范围查询,联合索引的最左匹配原则会一直向右匹配,但是,当遇到范围查询时就会停止匹配。也就是范围查询的字段可以用到联合索引,但是范围查询字段后面的字段无法用到联合索引。
范围查询主要包括(>, <, >=,<= , between and , like)几种情况,分别对这几种情况用实验的方式加以验证。
1. 例一
Q1
select * from t where a>1 and b=2, 联合索引(a,b)哪个字段用到了联合索引?
下图为联合索引(a,b)的 B+Tree结构图
联合索引是先按照a字段的顺序排序的,所以符合a>1的二级索引记录肯定时相邻的,于是在进行索引扫描时,可以快速定位到满足a>1的条件的第一条记录。然后,沿着记录所在的链表继续向后扫描,直到扫描到不符合a>1的条件为止。所以a字段是可以用到联合索引的。
但是在符合a>1的二级索引记录的范围里,b字段是无序的。
a=4 时 b=9;
a=5 时 b=2;
a=6 时 b=1;
因此,不能直接根据查询条件b=2来减少扫描记录数量,需要对b字段做全局扫描(b字段无法使用联合索引)。
可以通过执行计划中的 key_len来验证一下,通过key_len我们可以知道优化器具体使用了联合索引中的几个字段来形成扫描区间的边界条件
可以看到 key_len的长度为5个字节(因为字段允许为NULL,所以字段类型占用了1个字节,如果不允许为空就是4个字节),说明只有a字段使用了联合索引进行查询。而且可以看出,即使b字段没有用到联合索引,但是,key值为 idx_a_b,说明Q1查询语句使用了联合索引 idx_a_b
通过Q1可以得出一个结论:联合索引在遇到a字段的 > 的范围查询时就停止匹配,b字段不会用到联合索引。
2. 例二
Q2
select * from t where a>=1 and b=2, 联合索引(a,b)哪个字段用到了联合索引?
Q2和Q1的查询语句很像,唯一的区别就是a 字段的查询条件变成了 大于等于
联合索引是先按照a字段的顺序排序的,所以符合a>=1的二级索引记录肯定时相邻的,于是在进行索引扫描时,可以快速定位到满足a>=1的条件的第一条记录。然后,沿着记录所在的链表继续向后扫描,直到扫描到不符合a>=1的条件为止。所以a字段是可以用到联合索引的。
虽然在符合a>=1的二级索引记录里,b字段值是无序的,但是对于符合a=1的二级索引记录的范围里,b字段值是有序的(因为联合索引是先按照a字段的顺序排序,在a字段值相同的情况下,再按照b字段的值进行排序)
因此,在二级索引记录的a字段值为1时,可以通过b=2条件来减少需要扫描的二级索引记录范围,也就是b字段可以用到联合索引。
同样通过执行计划来验证一下
执行计划中显示 key_len为10,由于a和b两个字段都允许为空,因此都各自占用5个字节。说明优化器使用了两个字段的查询条件来形成扫描区间的边界条件,也就是a和b两个字段都用到了联合索引。
通过Q2可以得出一个结论:联合索引没有在遇到a字段的 >= 的范围查询时就停止匹配,b字段可以用到联合索引。
3. 例三
Q3
select * from t where a between 2 and 7 and b=2, 联合索引(a,b)哪个字段用到了联合索引?
由于 MySQL中 between and 等同于 >= and <=,因此 Q3和Q2 对联合索引的使用是相同的。
通过执行计划验证一下:
可以看到 key_len 长度为10,所以a和b两个字段都用到了联合索引。
通过Q3可以得出一个结论:联合索引没有在遇到a字段的 between and 的范围查询时就停止匹配,b字段可以用到联合索引。
4. 例四
Q4
select * from t1 where name like 'b%' and age=20, 联合索引(name,age)哪个字段用到了联合索引?
联合索引首先按照name字段的值进行排序,所以前缀为 'b'的 name字段的二级索引值都是相邻的。在进行索引扫描时,可以定位到前缀为 'b'的第一条记录,然后沿着记录所在的链表继续向后扫描,直到扫描到记录的name字段前缀不为 'b' 为止。
所以,name字段在联合索引中的扫描区间时 ['b','c')。注意 b 是闭区间。
虽然,age字段在全局范围是无序的,但是在name like 'b%' 找个索引记录的范围里,age字段值是有序的。于是可以通过age=20来减少扫描记录的范围,也就是说age字段可以用到联合索引。
查看一下执行计划来验证一下
从执行计划中可以看出,key 为idx_name_age, key_len为48。
name 字段类型为varchar(10),允许为空,字符集为utf8mb4。每个字符占4个字节,变长的字符串类型占2个字节,允许为空占1个字节。name字段的长度10*4+2+1=43字节
age字段类型为int 占4个字节,允许为空占1个字节。age字段长度为5字节
id_name_age 的长度为48个字节,这么name和age字段都用到了联合索引。
通过Q4可以得出一个结论:联合索引没有在遇到name字段的 like 查询时就停止匹配,age字段可以用到联合索引。
总结
联合索引的最左匹配原则,在遇到范围查询 大于或者小于 的时候,就会停止匹配。也就是范围查询的字段可以用到联合索引,但是范围查询后面的字段无法使用联合索引。注意对于 >=, <=, between and, like前缀匹配 这几类范围查询,不会停止匹配,范围查询后面的字段可以用到联合索引。