基础概念
对一组数据查找唯一值(distinct),数据满足下面条件时,可以使用 Index Skip Scan 技术来加快扫描。
- 数据有序存储;
- 数据的唯一值很少
例如下面的语句中,c1 唯一值很少,我们想求 c1 的唯一值集合,最简单的方式就是全表扫描一遍,然后利用 hashset 来计算出唯一值集合。但是这种方式太过暴力,如果c1列唯一值少,可以通过二分查找的方式逐个查找最小的 c1,从而跳过2个唯一值之间大量的行。
create table t1(c1 int, c2 int, c3 int, primary key(c1,c2));
-- NDV(c2) >> NDV(c1)
select distinct(c1) from t1;
这种扫描方式就叫做 Index Skip Scan,之所以如此命名,是因为:
- “Index”:因为 Index 具备数据有序存储的特征
- “Skip” :二分的方式来跳着查找,所以是 skip
- “Scan”:这个技术背后的本质目的还是扫描出需要的所有数据,所以还是 Scan
除了求 distinct 值,Index Skip Scan 还可以用于多种场景:
- 未指定主键前缀的扫描
下面的语句里,如果我们能快速得到 distinct(c1),那么就可以很轻松地构造出所有可能符合条件的结果集 { distinct(c1) union 10 },得到多个扫描 range,然后用这些 range 去 t1 中扫描。
create table t1(c1 int, c2 int, c3 int, primary key(c1,c2));
-- NDV(c2) >> NDV(c1)
select * from t1 where c2 = 10;
- 未指定索引前缀的扫描
下面的 sql 中没有指定索引前缀 c1,默认情况下是不能走到 idx 索引的。但是,如果已知 NDV(c3) >> NDV(c1),那么我们还是可以类似于“未指定主键前缀的扫描” 的方式,从 idx 中使用 Index Skip Scan 技术扫出所有 c1,然后构造出多个代表索引前缀的 range,从索引中扫出结果。
create table t1(c1 int, c2 int, c3 int, primary key(c1,c2));
create index idx on t1(c1,c3);
-- NDV(c3) >> NDV(c1)
select * from t1 where c3 = 10;
在这两种场景里,通过 Index Skip Scan 技术收集到多个扫描条件,相当于把索引表拆分成了多个子索引,然后用这些扫描条件分别去子索引表里去快速扫描。
实现考虑
实现 Index Skip Scan 需要:
- 计划生成阶段探测 NDV,符合条件时生成 range 框架下压到存储层。
- 存储层根据优化器的指示,收集前缀的 distinct 值,然后将得到的结果填入 range 框架内,然后基于 range 做扫描。
Note:本文的理解仅供自己参考。Index Skip Scan 另一种理解方式是,构造出很多子索引前缀,跳着在索引上扫描数据。也是合理的。
我写的小程序广告:
如果你朋友或亲戚家有小朋友在读一年级,这个口算天天练打印免费网站送给你:https://reactshare.cn/kousuan/