MySQL 的 Innodb 引擎中,索引是通过 B+ 树来实现的。不管是普通索引还是联合索引,都需要构造一个 B+ 树的索引结构。
道普通索引的存储结构中在 B+ 树的每个非节点上记录的索引的值,而这棵 B+ 树的叶子节点上记录的是聚簇索引(主键索引)的值。例如 AGE 为普通索引,ID 为主键索引如下
如果是联合索引的话,这棵 B+ 树又是如何存储的呢?在联合索引中,联合索引 (age,name) 也是一个 B+ 树,非叶子节点中记录的是 age,name两个字段的值,叶子节点中记录的是 age,name两个字段以及主键id的值。
如上图在存储的过程中,如上图所示,当 age 不同时,按照 age 排序,当 age 相同时,则按照name 排序。
由此得出结论
所引如果是联合索引的话,在构造B+树的时候,会先按照左边的 key进行排序,左边的 key 相同时再依次按照右边的 key 排序。在通过索引查询的时候,也需要遵守最左前缀匹配的原则,也就是需要从联合索引的最左边开始进行匹配,这时候就要求查询语句的where条件中,包含最左边的索引的值。
MySQL一定是遵循最左前缀匹配的,这句话在以前是正确的,但是在MySQL 8.0出现了索引跳跃扫描。
MySQL 8.0.13 版本中,对于range查询引入了索引跳跃扫描(Index Skip Scan)优化,支持不符合组合索引最左前缀原则条件下的SQL,依然能够使用组合索引,减少不必要的扫描。
首先有下面这样一张表:
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL);
CREATE INDEX idx_t on t1(f1,f2);
INSERT INTO t1 VALUES
(1,1), (1,2), (1,3), (1,4), (1,5),
(2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;
通过上面的SQL,先创建一张 t1 表,并把 f1,f2 两个字段设置为联合索引。之后再向其中插入一些记录。查询 t1表,where 条件为 f2 > 40
在 MySQL 5.7 版本,上述SQL的执行主要逻辑是从索引中取出所有的记录,然后按照 where 条件 f2 > 40 进行过滤,最后将结果返回。
在MySQL 8.0 版本,上述SQL使用索引 range 扫描,代替全索引扫描,对于每一个 f1 字段的值,进行 f2 范围扫描。
对于上述官方文档给出的例子,8.0版本SQL执行过程如下:
一直扫描完 f1 字段所有的唯一值,最后将结果合并返回。MySQL 8.0 使用这种策略会减少访问的行数,因为会跳过不符合构造范围的行。
也就是说,最终执行的SQL语句是像下面这样的:
SELECT f1, f2 FROM t1 WHERE f1 = 1 AND f2 > 40;
union all
SELECT f1, f2 FROM t1 WHERE f1 = 2 AND f2 > 40;
限制条件