【GreatSQL优化器-16】INDEX_SKIP_SCAN
一、INDEX_SKIP_SCAN介绍
GreatSQL 优化器的索引跳跃扫描(Index Skip Scan) 是一种优化查询的技术,尤其在联合索引中用于减少扫描的无效行数。它通过"跳跃"式的扫描方式,避免了对索引中无用部分的扫描,从而提升查询效率。这种技术适合特定场景,并有一定的优缺点。
索引跳跃扫描利用的是联合索引中非首列(非最左前缀)的索引列,来提高查询效率。例如,如果你有一个复合索引 (A, B),在传统的 B-Tree 索引中,只有当查询条件包含 A 列时,索引才会生效。但在跳跃扫描中,即使 A 没有出现在查询条件中,仍然可以通过扫描 B 列来有效查询。跳跃扫描会逐步扫描 A 列的每一个可能值,然后在每个 A 值下查找 B 列中符合条件的记录。这样避免了扫描大量无关记录,提升了查询性能。
下面用一个简单的例子来说明INDEX_SKIP_SCAN怎么应用在优化器。
CREATE TABLE t1(c1 INT, c2 INT, c3 INT, c4 INT);
CREATE UNIQUE INDEX i1_t1 ON t1(c1,c2,c3);
INSERT INTO t1 VALUES (1,1,1,1), (1,1,2,2), (1,3,3,3), (1,4,4,4), (1,5,5,5),
(2,1,1,1), (2,2,2,2), (2,3,3,3), (2,4,4,4), (2,5,5,5);
INSERT INTO t1 SELECT c1, c2, c3+5, c4+10 FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+10, c4+20 FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+20, c4+40 FROM t1;
INSERT INTO t1 SELECT c1, c2, c3+40, c4+80 FROM t1;
ANALYZE TABLE t1;
-- 下面的结果用到了skip scan
greatsql> EXPLAIN SELECT c1, c2 FROM t1 WHERE c2 > 40;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------+
| 1 | SIMPLE | t1 | NULL | range | i1_t1 | i1_t1 | 10 | NULL | 53 | 100.00 | Using where; Using index for skip scan |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------+
/*运行过程:
1、先找到第一列的不同值,得到结果为1和2
select distinct c1 from t1;
2、根据第一列的分组结果在分组内执行范围扫描
select c1, c2 from t1 where c1 = 1 and c2 > 40;
select c1, c2 from t1 where c1 = 2 and c2 > 40;*/
二、get_best_skip_scan代码解释
skip scan 是根据条件生成 mm tree 以后,根据 mm tree 结果和索引进行判定能不能用 skip scan,其中联合索引中范围条件列之前的列为prefix列,即用来作为分组的依据的列,如果有多个除了第一列之外的范围列,那么选取遇到的第一个列之前的列为前缀进行分组。
int test_quick_select() {
// 先判断skip_scan是否满足条件,不满足的话执行索引合并。
AccessPath *skip_scan_path = get_best_skip_scan();
// 不满足skip_scan执行索引合并,相关知识看上一节介绍
}
AccessPath *get_best_skip_scan() {
// 先判断sql是否支持skip_scan,见表一
// 接着开始执行skip_scan,遍历所有索引
for (uint cur_param_idx = 0; cur_param_idx < param->keys; ++cur_param_idx) {
// covering_keys代表可以使用的联合索引,sql涉及的所有列必须都在联合索引上才会有值
if (!table->covering_keys.is_set(cur_index)) {
cause = "query_references_nonkey_column";
goto next_index;
}
// 从mm tree抽出当前联合索引相关的SEL_ROOT,如果没有那么就不能执行skip scan
cur_index_range_tree = get_index_range_tree(cur_index, tree, param);
// 遍历当前索引涉及的所有列
for (cur_part = cur_index_info->key_part; cur_part != end_part;
cur_part++, part++) {
// 从mm tree获取当前列相关的SEL_ROOT,就是查询条件涉及的列
get_sel_root_for_keypart();
1、如果找到联合索引前缀列相关条件并且不是等号条件,那么就判断为"prefix_not_const_equality"并且跳到下一个索引
2、如果找到联合索引前缀列以外的列相关条件,以遇到的第一个条件作为skip scan的条件。
}
// 针对联合索引的前缀列估计范围内包含的行数,用在条件包含前缀列有等号条件的情况
quick_prefix_records = check_quick_select();
// 计算skip_scan的行数和cost
cost_skip_scan();
// 最后生成AccessPath,包含IndexSkipScanParameters
}
}
// 获取skip scan的行数和cost结果
void cost_skip_scan() {
table_records = table->file->stats.records;
// 如果可以根据前缀列估算出行数,那么就用估算的值,如果不能就用全表的行数。
if (quick_prefix_records ==

最低0.47元/天 解锁文章
1171

被折叠的 条评论
为什么被折叠?



