Oceanbase查询改写:or展开

概述

当查询语句的or条件中存在多个不同列的条件时,由于无法选择有效的索引,执行时可能会退化成全表扫描。为此,Oceanbase中定义了or展开规则,能够将满足条件的查询语句依据or条件展开为集合语句,以充分利用索引,提升查询性能。

基本原理

考虑如下情况:

SELECT * FROM t1 WHERE t1.c1 = 0 OR t1.c2 < 200

对于上述查询,如果or条件中的列都建有索引,那么可以对其进行改写以充分利用索引,如下所示:

SELECT * FROM t1 WHERE t1.c1 = 0
UNION ALL 
SELECT * FROM t1 WHERE t1.c2 < 200 AND t1.c1 <> 0

代码解析

or展开规则的入口为ObTransformOrExpansion::transform_one_stmt,执行流程如下:

  1. 调用check_stmt_validity函数检查查询语句中是否有可以被改写的表达式,该函数会遍历where条件的表达式,然后调用is_condition_valid函数判断当前表达式是否可以被展开。

  1. 调用get_trans_view函数将原查询语句拆分成父查询和spj子查询,后者被用于创建集合语句。如果原查询本身属于spj查询,则不进行拆分。

  1. 调用prepare_or_condition函数收集where条件中能够被改写的表达式,分别放入候选集合中。其中包含子查询条件的表达式改写优先级较低,会被放在候选集的尾部。

  1. 遍历候选表达式集合,按照如下流程执行改写:

  1. 调用preprocess_or_condition函数对表达式进行预处理。

  1. 调用transform_or_expansion函数基于前面的spj子查询将表达式展开改写为集合语句。

  1. 调用merge_stmt函数将集合语句合并到父查询中。

  1. 调用accept_transform函数对改写后的执行开销进行评估,以此判断是否应该接受改写。

is_condition_valid函数会结合查询语句对表达式进行检查,能够被改写的表达式需要为or或者in类型,且需要满足下列条件之一:

  1. 查询语句中同时包含order by和limit表达式(下称topk条件)。

  1. 表达式的子表达式涉及多个列。

  1. 表达式的子表达式含有子查询条件。

对于or类型的表达式,该函数会进一步检查其子表达式是否满足下列条件之一:

  1. 子表达式包含子查询。

  1. 子表达式属于join条件的一部分。

  1. 子表达式涉及的列能够找到匹配的索引。

  1. 子表达式涉及的表不超过一张,而父表达式涉及多张表(如t1.c1 <> 0 or t2.c1 <> 0)。

对于in类型的表达式,需要同时满足下列条件:

  1. 表达式的右参数都为常量(如t1.c1 in(0, 1, 1))。

  1. 表达式不包含子查询。

preprocess_or_condition函数首先会将or类型的表达式中包含子查询的子表达式移动到参数列表的尾部,如果表达式只涉及同一列或者参数中存在两个以上包含子查询的子表达式,则需要进一步调用check_can_set_stmt_unique函数对当前spj查询进行检查。这里主要是判断能够使用union而非union all来作为集合操作,如果满足条件(下称can_union_distinct),则能够使用union对结果去重,而集合语句的每一条子查询都只需要提取or表达式中的一个参数表达式即可,否则还需要加上前面参数表达式的反条件。如果满足can_union_distinct,则进一步判断当前spj查询的输出是否满足唯一性(下称spj_is_unique)。如果表达式只涉及同一列且不满足can_union_distinct,则放弃改写该表达式。上述流程中涉及含子查询的子表达式的处理是为了尽量减少子查询条件被引入多条集合操作的语句中,因为存在两个以上该类型的参数,且不满足can_union_distinct时,则至少有一个该类型的条件会同时作为正反条件出现在两条语句中。

transform_or_expansion函数会遍历or表达式的参数表达式,对于每个参数表达式该函数会拷贝一份spj子查询(下称or_expansion_stmt),然后调用adjust_or_expansion_stmt函数对其进行改写。如果满足can_union_distinct且不满足spj_is_unique,则需要调用ObTransformUtils::recursive_set_stmt_unique函数对语句进行改写使得其能够返回唯一结果,这里的做法是根据from表类型增加相应的select列使得其满足要求。如果from表为基表,则增加唯一键;如果为视图表,则增加视图查询的所有select列。改写完成后,将其放入子查询集合中。待全部参数表达式改写完毕后,则使用该集合创建集合语句。

adjust_or_expansion_stmt函数会根据表达式的类型,使用当前参数偏移提取并构建查询条件,然后加入or_expansion_stmt的where条件中。对于or类型的表达式,会调用create_expr_for_or_expr函数进行处理;对于in类型的表达式,会调用create_expr_for_in_expr函数进行处理。上述操作完成后,该函数会清理or_expansion_stmt中由于or表达式展开不再使用的子查询。

create_expr_for_or_expr函数会遍历or表达式的参数表达式中,偏移小于等于当前偏移的参数。对于当前参数,会将其展开后放入表达式集合中;对于小于当前偏移的的参数,如果不满足can_union_distinct,则使用lnnvl表达式对原表达式取反后加入表达式集合。

create_expr_for_or_expr函数的逻辑与前者基本相同,如果满足can_union_distinct,则使用当前偏移对应的in表达式常量构建equal条件,加入表达式集合中;如果不满足则还需要为小于当前偏移的常量也构建equal表达式,然后在外层创建lnnvl表达式取反后加入表达式集合。

merge_stmt函数负责将集合语句合并到父查询中。如果原查询语句没有进行拆分,且改写过程中没有引入新的select列,则直接使用集合语句;如果原查询语句没有进行拆分,但是引入了新的select列(如基表的唯一键),则将集合语句转化为视图表,然后从视图表的父查询中移除多余的select列;如果原查询语句进行了拆分,则使用集合语句替换父查询的视图表即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值