索引跳跃扫描

前言

MySQL 的 Innodb 引擎中,索引是通过 B+ 树来实现的。不管是普通索引还是联合索引,都需要构造一个 B+ 树的索引结构。

道普通索引的存储结构中在 B+ 树的每个非节点上记录的索引的值,而这棵 B+ 树的叶子节点上记录的是聚簇索引(主键索引)的值。例如 AGE 为普通索引,ID 为主键索引如下

索引跳跃扫描_第1张图片

如果是联合索引的话,这棵 B+ 树又是如何存储的呢?在联合索引中,联合索引 (age,name) 也是一个 B+ 树,非叶子节点中记录的是 age,name两个字段的值,叶子节点中记录的是 age,name两个字段以及主键id的值。

索引跳跃扫描_第2张图片
如上图在存储的过程中,如上图所示,当 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.19,执行计划Extra字段为:Using where; Using index
  • MySQL 8.0.20,执行计划Extra字段为:Using where; Using index for skip scan

在 MySQL 5.7 版本,上述SQL的执行主要逻辑是从索引中取出所有的记录,然后按照 where 条件 f2 > 40 进行过滤,最后将结果返回。

在MySQL 8.0 版本,上述SQL使用索引 range 扫描,代替全索引扫描,对于每一个 f1 字段的值,进行 f2 范围扫描。

对于上述官方文档给出的例子,8.0版本SQL执行过程如下:

  1. 获取 f1 字段第一个唯一值,也就是 f1 = 1
  2. 构造 f1 = 1 and f2 > 40,进行范围查询
  3. 获取 f1 字段第二个唯一值,也就是 f1 = 2
  4. 构造 f1 = 2 and f2 > 40,进行范围查询

一直扫描完 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;

限制条件

  • 查询必须只能 依赖 一张表,不能多表JOIN。
  • 查询中不能使用 GROUP BY 或 DISTINCT 语句。
  • 查询的字段必须是索引中的列。
  • 组合索引形式:([A_1, …, A_k,] B_1, …, B_m, C [, D_1, …, D_n]),A,D 可以为空,但是B ,C 不能为空。

你可能感兴趣的:(其他,java)