面试官:你竟然告诉我,联合索引不用遵循最左前缀原则?
联合索引的最左前缀原则,应该是MySQL方向的一道高频且入门级别的面试题了。一般情况下,考查对象是校招生或刚刚工作不久的候选人。
面试场景大致如下:
面试官:“建过MySQL的联合索引吧?可以说说它的最左前缀原则吗?”
候选人:“好的。如果我们为一张表创建联合索引(a,b,c),当执行SQL语句进行查询的时候,必须按照该索引从左往右的顺序进行匹配,否则就使用不到该索引了。
举例如下:
// 可以用到索引
select * from table1 where a = 2;
// 可以用到索引
select * from table1 where a = 2 and b = 4;
// 可以用到索引
select * from table1 where a = 2 and b = 4 and c = 5;
// 可以用到索引,跟 where条件中的先后顺序没关系
select * from table1 where b = 4 and a = 2 and c = 5;
// 用不到索引
select * from table1 where c = 5;
// 用不到索引
select * from table1 where b = 4 and c = 5;
// 用不到索引
select * from table1 where c = 5;
还有就是,联合索引的先导列(最左边的列)是非常有讲究的,很多同学都知道要选择区分度高的列作为先导列,这个没错儿。
另外一个需要考虑的点是,同时也需要考虑查询命中率的问题。
举个例子,字段A比字段B的区分度高一些,但平均每100次查询中,有80次查询需要用到B字段,只有10次查询能用到A字段,那我们还是应该选择B字段作为联合索引的先导列。
面试官:“很好,你对于这块的知识点掌握得很牢固,我来问下一个问题。”
候选人:“我还没说完呢,其实在MySQL 8.0.13 版本以后,就算不遵循最左前缀原则,一样可以使用上索引。”
面试官:“什么?你竟然告诉我,联合索引不用遵循最左前缀原则?”
这的确是真实情况,在MySQL 8.0.13 版本引入了Skip Scan Range Access Method,它在一定条件下可以不遵守最左前缀原则,通过范围扫描的方式来代替了全表扫描。
下面我们就来深入讲解一下,嗯,以后通过这种方式来回怼面试官,酷毙了有木有?
Skip Scan Range Access Method
接下来我们直接show me the code,通过示例的方式带大家去理解这项特性。
第一步,先创建一张table1表,表结构如下:
CREATE TABLE `table1` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`a` int DEFAULT NULL,
`b` int DEFAULT NULL,
`c` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a_b_c` (`a`,`b`,`c`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
表结构很简单,里面有一个关键点,那就是(a,b,c)三个字段组成的联合索引。
第二步,我们往table1表中填充10条数据:
INSERT INTO table1 (a,b,c) VALUES
(1,1,1), (1,2,2), (1,3,3), (1,4,4), (1,5,5),
(2,1,1), (2,2,2), (2,3,3), (2,4,4), (2,5,5);
请注意填充的数据,字段a在整张表中只有两个值(1,2),而字段b和c在整张表中有五个值(1,2,3,4,5),所以从字段区分度来讲,字段a的区分度不如字段b和c高。
第三步,我们在表中多造一些数据,执行下面的四条SQL语句:
INSERT INTO table1 (a,b,c) SELECT a, b + 5, c + 5 FROM table1;
INSERT INTO table1 (a,b,c) SELECT a, b + 10, c + 10 FROM table1;
INSERT INTO table1 (a,b,c) SELECT a, b + 20, c + 20 FROM table1;
INSERT INTO table1 (a,b,c) SELECT a, b + 40, c + 40 FROM table1;
执行过后,表中目前共有160条记录,但字段a在整张表中仍然只有两个值(1,2)。
第四步,执行如下SQL语句,主要是为了测试如果不按照最左前缀模式,是否还能够使用到联合索引idx_a_b_c。
EXPLAIN SELECT * FROM table1 WHERE b = 5;
通过执行计划我们可以看到,该SQL语句确实用到了idx_a_b_c。
但请注意看,该执行计划的type = index,代表走的全索引扫描,这是因为联合索引中包括了表中的所有字段,所以不需要再进行回表了,其实意义并不大。
嗯,说好的Skip Scan Range Access Method哪里去了?别急,我们继续往下看。
第五步,执行ANALYZE TABLE,如下图所示:
关于MySQL中ANALYZE语句的作用,我在这里解释一下。
执行MySQL ANALYZE语句,可进行表中统计信息的收集更新,从而使查询优化器生成更加准确的执行计划,以达到提升查询性能的目的。
比如:以上述table1表为例,可以收集到表中共有160条数据,其中a字段有(1,2)两个值,每个值各有80条数据。而b,c字段有(1,2....,79,80)80个值,每个值各有两条数据。
根据收集而来的这些信息,则可以更好地计算table1表中每个字段的区分度。
第六步,再次执行如下SQL语句,测试如果不按照最左前缀模式,是否还能够使用到联合索引idx_a_b_c。
EXPLAIN SELECT * FROM table1 WHERE b = 5;
见证奇迹的时刻来了,请见Extra列,里面包含 Using index for skip scan,代表这次的执行计划走了 Skip Scan Range Access Method。
Skip Scan 底层实现原理
我们继续往下深究,这种无视“最左前缀原则”的方式,到底是如何实现的呢?难道查询优化器会未卜先知不成?
非也,其实它用的是穷举法。
就像有个妹子,你知道她的名字叫“静静”,但不知道姓氏。那很简单,张静静、李静静、马静静、王静静、黄静静、林静静,你按照百家姓去猜就好了,最后总能命中的。
而上文所说的,table1表中的a字段只有(1,2)两个值,那索性将其填充上,不就遵循最左前缀原则了吗?
SELECT * FROM table1 WHERE a = 1 and b = 5;SELECT * FROM table1 WHERE a = 2 and b = 5;
嗯,如果是这种区分度比较低、值的数量比较少的字段,确实非常适合用这种方式进行优化,会比type = index的全索引扫描快很多。
不过,如果字段区分度很高的场景就不适合了,因为这样会繁衍出来太多的SQL语句,反而是适得其反的。
说白了,所谓的Skip Scan Range Access Method,只不过是在最左前缀原则的基础上,进行了一波优化而已。
Skip Scan 的局限性
根据MySQL的官方文档记载,Skip Scan Range Access Method在使用上还是有很多限定条件的。
限定条件包括:必须创建了联合索引、必须是单表查询、不能使用DISTINCT 和GROUP BY 关键字、只能使用覆盖索引、查询条件必须是常量。
我认为这里面,限制最大的就是只能使用覆盖索引,不支持回表操作了。
- 点赞
- 收藏
- 关注作者
评论(0)