Oceanbase查询改写:连接消除

概述

通常情况下,如果查询语句中存在较多的连接操作,会对查询性能有较大的影响。为此,Oceanbase中定义了连接消除规则,能够识别和消除不必要的连接,提升查询性能。

基本原理

连接消除规则主要包含对以下几种情况的处理:

  1. 自/外键连接消除:如果查询语句的某个非join表(如基表或者视图表)与和它形成内连接关系的其他非join表或join表内的非join表存在自/外键关系,且前者没有引入新的列或可以合并到后者时,可以消除前者。

  1. 外连接消除:如果查询语句中存在某个外连接,其连接条件中包含右表唯一键(包含主键)的equal条件,且连接结果中不包含右表列时,可以消除该外连接。

  1. 自/外键半(反)连接消除:如果查询语句中存在某个半(反)连接,其左右两表之间存在自/外键关系时,可以消除该外连接。

自/外键连接消除

不同类型子查询合并主要分为两种情况:

  1. 自键连接消除:如果查询语句的某个非join表(如基表或者视图表)与和它形成内连接关系的其他非join表或join表内的非join表属于同一张表,同时连接条件中包含唯一键(包含主键)的equal条件,且前者的列可以合并到后者时,可以消除前者。

  1. 外键连接消除:如果查询语句的某个非join表(如基表或者视图表)与和它形成内连接关系的其他非join表或join表内的非join表间存在外键参照,同时连接条件中包含主外键的equal条件,且前者没有引入主键以外的列时,可以消除前者。

自键连接消除

考虑如下情况:

SELECT c.c2, t1.c2, t2.c2 FROM t1 AS c, (t1 LEFT JOIN t2 ON t1.c1 = t2.c1) 
    WHERE c.c1 = t1.c1

上述查询中from子句中的两张表为隐式的内连接关系,而where条件则可以看作内连接的连接条件。如果其中的equal条件使用的是基表的唯一键,则可以通过改写将基表移除,如下所示:

SELECT t1.c2, t1.c2, t2.c2 FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1

外键连接消除

考虑如下情况:

SELECT t1.c1, t2.c2, t3.c2 FROM t1, (t2 LEFT JOIN t3 ON t2.c1 = t3.c1) 
    WHERE t1.c1 = t3.c1

与自键连接的情况类似,如果上述查询中from子句中左连接的左表(即t2表)通过外键依赖左基表(即t1表),由于select列表中没有引入基表的主键以外的列,因此可以通过改写将基表移除,如下所示:

SELECT t3.c1, t2.c2, t3.c2 FROM t2 LEFT JOIN t3 ON t2.c1 = t3.c1

外连接消除

考虑如下情况:

SELECT t1.* FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1

对于上述查询,由于select列中不包含右表列,如果连接条件中的右表列为主键或唯一键,可以移除连接操作,如下所示:

SELECT * FROM t1

自/外键半(反)连接消除

不同类型子查询合并主要分为两种情况:

  1. 自键半连接消除:如果查询语句中存在某个半(反)连接,其左右两表属于同一张表或左表包含于右表,且连接条件中包含唯一键(包含主键)的equal条件时,可以消除连接。

  1. 外键半连接消除:如果查询语句中存在某个半(反)连接,其左右两表间存在外键参照,同时连接条件中包含主外键的equal条件时,可以消除连接。

自键半(反)连接消除

考虑如下情况:

SELECT * FROM t1 AS c SEMI JOIN t1 ON c.c1 = t1.c1 AND t1.c2 <> 0

上述查询中的左右两表属于同一基表,如果连接条件中包含唯一键的equal条件,可以移除连接操作,如下所示:

SELECT * FROM t1 AS c WHERE c.c2 <> 0

外键半(反)连接消除

考虑如下情况:

SELECT * FROM t1 SEMI JOIN t2 ON t1.c1 = t2.c1

对于上述查询,如果左表通过外键依赖右表,且连接条件中包含主外键的equal条件,可以移除连接操作,如下所示:

SELECT * FROM t1

代码解析

连接消除规则的入口为ObTransformJoinElimination::transform_one_stmt,执行流程如下:

  1. 调用eliminate_join_self_foreign_key函数执行自/外键连接消除。

  1. 调用eliminate_outer_join函数执行外连接消除。

  1. 调用eliminate_semi_join_self_foreign_key函数执行自/外键半(反)连接消除。

  1. 调用trans_self_equal_conds函数清理由于前几步消除操作产生的自相等表达式。

自/外键连接消除

eliminate_join_self_foreign_key函数执行流程如下:

  1. 调用eliminate_join_in_from_base_table函数在from列表中的表之间执行自/外键连接消除。

  1. 调用eliminate_join_in_joined_table函数对各个join表分别执行自/外键连接消除。

eliminate_join_in_from_base_table函数执行流程如下:

  1. 调用ObSelectStmt::get_equal_set_conditions函数获取查询语句各部分的条件表达式(下称conds)。

  1. 调用extract_candi_table函数分别抽取查询语句中from列表中的非join表(下称candi_tables)和join表中的非join表(即叶子节点集合,下称child_candi_tables),后面的消除操作主要针对前者进行。

  1. 调用eliminate_candi_tables函数对上一步得到的结果执行判断和消除。

eliminate_join_in_joined_table函数遍历from列表中的join表,然后递归地遍历join树。在递归过程中,可能会将子节点下的叶子节点加入候选集合(下称child_candi_tables),提升到inner join类型的父节点。内连接条件则会加入到转换条件集合(下称trans_conditions),提升到父节点的连接条件或进一步提升到where条件中。针对不同的节点类型,处理方式如下所示:

  1. 非join节点(叶子节点):将当前节点加入child_candi_tables。

  1. full join节点:递归地调用extract_candi_table抽取子节点加入child_candi_tables。

  1. left join或right join节点:对子节点递归调用eliminate_join_in_joined_table函数,调用完成后,会将左右子树下的child_candi_tables合并到本层。对于left join,会将左子树下的trans_conditions提升合并到本层,同时用右子树下内连接子节点的trans_conditions替换当前join节点的连接条件;对于right join,则刚好相反。

  1. inner join节点:首先调用classify_joined_table函数对当前节点下的inner join子节点进行递归遍历,将子节点划分到inner join、outer join及非join节点集合,同时将内连接条件收集到连接条件集合中。然后遍历outer join节点集合,为每个节点调用eliminate_join_in_joined_table函数以收集该节点下的候选节点和内连接条件集合,分别合并到当前层的child_candi_tables和内连接条件集合(下称conds)中。完成上述工作后,会调用eliminate_candi_tables函数对非join节点集合中的表进行判断和消除。最后,调用rebuild_joined_tables函数重新构建当前节点下的join树,同时将内连接条件传递给trans_conditions以向上提升到父节点的连接条件或where条件中。

由于from列表中的表实质上等同于隐式的内连接,因此前面提到的from列表中的执行过程可以看成本过程的特例,即初始join表为inner join表的情况。

eliminate_candi_tables函数会通过遍历将candi_tables集合内的表进行两两比较,同时将每一张表与child_candi_tables集合中的每一张表进行两两比较,这里用target_table指代candi_tables集合中待消除的表,source_table指代candi_tables或child_candi_tables集合中的对照表。对于每一组的两张表,调用do_join_elimination_self_key函数判断并执行自键连接消除。后者的执行流程如下:

  1. 调用ObTransformUtils::check_loseless_join函数两表是否满足如下loseless_join条件(结合上下文,这里指的应该是连接过程中不会发生数据”损失“,即对于每条左表记录都能找到唯一的右表记录):

  1. 左表(source_table)包含于右表(target_table)。

  1. 能够基于由conds中的条件构建的EqualSet从两表的列中抽取出构成equal条件的列,形成逻辑上的连接条件,且连接条件的列对于两表均满足唯一性。对于抽取过程这里简单举例说明:对于如下表达式语句t1.c1 = t3.c1 and t2.c1 = t3.c1 and t1.c2 = t2.c2 可以构成两个EqualSet,分别为<t1.c1, t2.c1, t3.c1>以及<t1.c2, t2.c2>。如果左右表分别为t1和t2,则可以抽取出t1.c1 = t2.c1 and t1.c2 = t2.c2作为连接条件。

  1. 调用adjust_table_items函数更新查询语句中的列依赖,对于视图表则需要在source_table中补充缺少的select列。

  1. 调用trans_table_item函数从查询语句中移除target_table依赖。

上述操作执行完成后,会调用get_eliminable_tables函数判断candi_tables集合中是否还有可以能够执行外键连接消除的表。该函数会通过遍历将candi_tables集合内剩余的基表进行两两比较,同时将每一张表与child_candi_tables集合中的每一张表进行两两比较,这里用target_table指代candi_tables集合中待消除的表,source_table指代candi_tables或child_candi_tables集合中的对照表。对于每一组的两张表,调用check_transform_validity_foreign_key函数判断是否能够执行外键连接消除,执行流程如下:

  1. 调用extract_equal_join_columns函数抽取equal连接条件。

  1. 调用ObTransformUtils::check_foreign_primary_join函数判断上述equal条件是否包含完整的外键约束。

  1. 调用check_all_column_primary_key函数判断待消除的表是否引入了外键以外的列,如果没有则可以被消除。

对于能够被消除的表,会调用do_join_elimination_foreign_key函数执行消除操作,后者执行流程如下:

  1. 调用trans_column_items_foreign_key函数更新外键列依赖。

  1. 调用trans_table_item函数从查询语句中移除target_table依赖。

外连接消除

eliminate_outer_join函数负责执行外连接消除,该函数最终会调用eliminate_outer_join_in_joined_table函数对from列表中的join表递归地执行消除操作,后者对join树的每个节点调用check_transform_validity_outer_join函数判断本节点是否包含可消除的外连接,执行流程如下:

  1. 调用ObTransformUtils::get_table_joined_exprs函数获取连接条件对应的右表列。

  1. 调用ObTransformUtils::check_exprs_unique函数判断上述右表列关于右表是否满足唯一性。

  1. 调用adjust_relation_exprs函数判断右表列中的列是否仅仅出现在连接条件中,如果满足则可以消除该外连接。

如果当前节点不包含可以被消除的外连接,则对于左右子树递归地调用eliminate_outer_join_in_joined_table函数。如果能够消除,则仅仅需要递归左子树,然后从查询语句中移除右表依赖,将当前表更改为左子树。

自/外键半(反)连接消除

eliminate_semi_join_self_foreign_key函数负责执行自/外键半(反)连接消除,该函数会遍历查询语句中的半/反连接信息,执行如下流程进行判断和消除:

  1. 获取用于关联判断的条件表达式集合(下称conds),对于半连接,调用ObSelectStmt::get_equal_set_conditions函数获取查询语句各部分的条件表达式;对于反连接,仅仅使用当前反连接的连接条件。这里存在差异的原因是,半连接的连接条件会直接并入where条件,所以可以和其他条件一起考虑,反连接则不然。

  1. 调用eliminate_semi_join_self_key函数执行自键半(反)连接消除。

  1. 调用eliminate_semi_join_foreign_key函数执行外键半(反)连接消除。

eliminate_semi_join_self_key函数首先会调用check_transform_validity_semi_self_key函数判断当前半(反)连接是否可以消除,如果可以则从查询语句中移除该连接信息,然后调用do_elimination_semi_join_self_key函数执行消除后的查询语句转换。

check_transform_validity_semi_self_key函数执行流程如下:

  1. 调用check_relations_containment函数判断左表是否包含于右表。

  1. 调用check_semi_join_condition函数检查连接条件中涉及右表的条件中是否仅包含左右两表相同列(因为两表实际为同一张表)的equal条件且没有单独的右表条件,并抽取相应上述equal条件中两表的列表达式。如果满足,则可以进行消除,否则需要进一步判断。对于反连接,如果存在上述equal条件和右表条件以外的其他条件(如t1.c1 = t2.c2),或右表条件中包含or嵌套,则不能进行消除。

  1. 调用ObTransformUtils::extract_table_exprs函数从conds中分别抽取左右两表的条件表达式,如果当前为反连接,则只抽取左表条件。

  1. TransformUtils::check_exprs_unique_on_table_item函数使用上一步抽取的列表达式和条件表达式判断对于对应表是否满足唯一性。如果满足,则可以进行消除。这里条件表达式的作用是作为schema唯一性的补充,假如t1表存在<c1,c2>联合主键,此时如果仅仅判断c1列显然不满足唯一性。但是如果条件表达式中存在c2 = 0这样的equal常量表达式,则依然可以认为满足唯一性。

do_elimination_semi_join_self_key函数执行流程如下:

  1. 调用trans_semi_condition_exprs函数执行连接条件转换。

  1. 调用adjust_table_items函数更新查询语句中的列依赖。

eliminate_semi_join_foreign_key函数首先会调用check_transform_validity_semi_foreign_key函数判断当前半(反)连接是否可以消除,如果可以则从查询语句中移除该连接信息,然后按照如下流程进行转换:

  1. 调用trans_semi_condition_exprs函数执行连接条件转换。

  1. 调用trans_column_items_foreign_key函数更新外键列依赖。

  1. 调用trans_semi_table_item函数从查询语句中移除右表依赖。

trans_semi_condition_exprs函数会将原半(反)连接中的连接条件合并到查询语句的where条件中,对于半连接,会将连接条件直接合并;对于反连接则需要按照如下流程进行转换:

  1. 将左表条件转换为lnnvl表达式。

  1. 对于相同列的equal表达式,如果该列为非空,则转换为false表达式,否则转换为is null表达式。

  1. 使用前面得到的表达式集合创建or表达式,合并到查询语句的where条件中。

可以看出,对于反连接的转换实质上是对连接条件取反。

trans_self_equal_conds函数会遍历where条件、join表及semi信息中的条件,移除由于连接消除产生的自相等条件。如果自相等的列不为非空(本身为nullable列或位于外连接的内表侧),则需要补充相应列的非空条件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值