前言
本文将详细讲述联合索引的最左匹配原则是啥一回事,为什么不匹配也能使用上联合索引?什么是全索引扫描,跟全表扫描有啥区别?
什么是联合索引
联合索引 == 复合索引 == 组合索引。
一句话就是:多个字段组合成一个索引。
什么是最左匹配原则
一句话:
最左匹配原则就是where后的字段(order by group by的字段也算)第一个开始,从联合索引的最左边开始匹配,依次往右,不能匹配不上,如果匹配不上了那么后面的字段(包括当前匹配不上的字段)就不能使用索引,只有连上的才能用索引。
正常演示
CREATE TABLE employee (
id INT NOT NULL AUTO_INCREMENT,
name CHAR(1) NOT NULL,
age INT NOT NULL,
salary int NOT NULL,
birthday DATE NOT NULL,
PRIMARY KEY (id),
INDEX idx_name_age_salary (name, age, salary)
);
上面的id为主键,(name,age,salary)三个字段组建成一个联合索引,birthday就是普通的字段。
执行1:EXPLAIN select * from employee where name = 'smith' and age = 18 and salary = 3000
这样的sql是能最左匹配上联合索引的,所以可以用上联合索引,并且三个字段都使用上了,key_len为11字节,三个字段刚刚好11个字节。
执行2:EXPLAIN select * from employee where name = 'smith' and age = 18
这样的sql是能最左匹配上联合索引的,所以可以用上联合索引,但是只有两个字段都使用上了,key_len为7字节,少了salary的4个字节。
执行3:EXPLAIN select * from employee where name = 'smith' and salary = 3000
可以看出这条sql根据最左匹配原则只有name匹配上了联合索引,而下一个salary匹配不上了,所以salary条件使用不上索引,也可以看出key_len只有三个字节刚刚好是name字段的字节数。
执行4:EXPLAIN select * from employee where salary = 3000
这句sql因为不符合最左匹配原则,都没匹上,所以type为all,all表示全表扫描,不走索引。
以上的演示都是正常情况下,让人看的懂,但是凡是有特殊,我们看看接下来的演示。
特殊演示:不符合最左原则也用上了联合索引
还是以上面的表来做特殊演示
执行5:EXPLAIN select salary from employee where salary = 3000
为什么不符合最左匹配原则,却用上了联合索引,并且key_len=11全用上了?
执行6:EXPLAIN select name,age,salary from employee where salary = 3000;
为什么不符合最左匹配原则,却用上了联合索引,并且key_len=11全用上了?
执行7:EXPLAIN select id,name,age,salary from employee where salary = 3000;
为什么不符合最左匹配原则,却用上了联合索引,并且key_len=11全用上了?
执行8:EXPLAIN select id,name,age,salary,birthday from employee where salary = 3000;
为什么不符合最左匹配原则,这里却用不了联合索引,type=all,是全表扫描,这是为什么?
我们可以看出执行5-7的type类型是index类型,该类型是全索引扫描,是从索引第一条一直扫描到最后,跟全表扫描有点像,但也有区别,后续讲区别。
可以看出以上的特殊sql只有查询出的字段有区别,细心的人可能已经看出了区别,那就是执行567走的是覆盖索引(Extra字段的using index,就表示覆盖索引),而执行8不是覆盖索引。
覆盖索引
所需数据覆盖了索引,不需要扫描data域中的行数据。
特殊情况:二级索引中如果需要查询主键,那也是覆盖索引,覆盖索引都不需要回表。
执行567能使用上联合索引的原因就是因为覆盖索引,我们想要的数据都在这颗联合索引上,不需要回表的操作,所以执行567使用上了联合索引,但走的是全索引扫描。
而执行8我们需要的数据联合索引满足不了,所以不是覆盖索引,只能走一个全表扫描。
特殊演示的总结
如果查询的字段覆盖了联合索引,并且where后的字段使用了联合索引中的字段,即使不满足最左匹配原则,也能走联合索引,走的是一个全索引扫描,会扫描整个索引树。
全索引扫描和全表扫描的区别
全索引扫描通过遍历索引树而不是直接访问data域,可以大大降低查询的 I/O 成本,因为索引树通常可以保存在内存中,而数据域可能较大并需要磁盘访问。这在某些情况下可以提高查询性能,特别是当查询只需要访问少量数据列时或者涉及到大型表的查询时。
测试一下
测试1:EXPLAIN select * from employee USE INDEX (p);
测试2:EXPLAIN select id from employee USE INDEX (p);
加上USE INDEX (p)是为了让查询优化器使用我们指定的主键索引,不让它自己决定使用哪个索引。
经过测试发现测试1的type=all是全表扫描,测试2的type=index是全索引扫描,可以看出当我们查询的数据覆盖了索引时,走的是全索引扫描,不会扫描data域中的值。
总结:
- 在只需要扫描索引值的时候就可能会出现全索引扫描的情况。
- 全索引扫描性能>>>>>全表扫描。
细心的小伙伴可能看出来我们之前的执行7,这个查询字段包括了id,但是id是主键,在二级索引中(非主键联合索引也是二级索引)叶子节点存储的data域中存储的是主键值。
全索引扫描不是不扫描data域吗,那么怎么拿到id的?
但是我们之前的执行7除了要联合索引上的索引值,还需要data域中的主键值,也是覆盖索引,并且走的也是全索引扫描,这是为什么?
具体情况我也不知道,但是我猜测如果是二级索引即使需要扫描data域中的主键值也是走全索引扫描,就跟覆盖索引一样是个特殊情况吧。
以上案例可以得出一个结论: 全索引扫描必是覆盖索引(全索引扫描符合覆盖索引的特征),而覆盖索引不一定是全索引扫描
例如:EXPLAIN select name,age,salary from employee where name = '11';
这条语句符合最左匹配原则,key_len=3,name字段使用上了索引,并且type是ref不是index全索引扫描,却是一个覆盖索引。
type=ref是什么意思就不说了,感兴趣的可以去搜一下EXPLAIN字段的详解。
其他特殊情况
EXPLAIN select name,age,salary from employee where age =11 and name = '11' and salary = 11;
EXPLAIN select name,age,salary from employee where name = '11' and salary = 11 and age =11;
EXPLAIN select name,age,salary from employee where salary = 11 and age= 11 and name = '11';
这里就只有一张图,因为其他的两张结果跟这张一模一样。
为什么上面的sql不符合最左匹配原则还都能用上联合索引,并且不是全索引扫描。
这是因为查询优化器会帮助我们把sql改写成可以使用上联合索引的样子,只要你的字段符合最左匹配原则,顺序就算不符合最左匹配原则,查询优化器也会帮助你修改顺序。
但是不建议顺序乱写,这会增加查询优化器的负担。