概述
与内连接不同,外连接不满足交换律结合律,因此在查询优化阶段不能根据实际数据表的大小选择最优的连接顺序。为此,Oceanbase中定义了外连接转内连接规则,能够将满足条件的外连接转为内连接,为优化器进一步优化提供了基础。
基本原理
外连接转内连接规则主要包含对以下两种情况的处理:
- 存在空值拒绝条件:当where条件中存在内表列的空值拒绝条件时,可以将外连接改写为内连接。
- 存在主外键参照:当外表通过外键依赖内表,且仅仅使用主外键的equal条件作为连接条件时,可以将外连接改写为内连接。
存在空值拒绝条件
考虑如下情况:
SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE t2.c2 IS NOT NULL
对于上述查询,由于where中存在空值拒绝条件,因此左表中匹配不上右表的结果会被该条件过滤掉,可以将上述查询中的左连接改写为内连接,如下所示:
SELECT * FROM t1 JOIN t2 ON t1.c1 = t2.c1 WHERE t2.c2 IS NOT NULL
存在主外键参照
考虑如下情况:
SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1
对于上述查询,如果左表通过外键依赖右表,且仅仅使用相应的主外键equal条件作为连接条件,那么对于每一条左表记录一定能够找到对应的右表记录,可以将上述查询中的左连接改写为内连接,如下所示:
SELECT * FROM t1, t2 WHERE t1.c1 = t2.c1
代码解析
外连接转内连接规则的入口为ObTransformEliminateOuterJoin::transform_one_stmt,该函数的主要执行流程如下:
- 调用ObDMLStmt::get_stmt_equal_sets函数获取当前查询语句的where,semi join以及join条件(不包含外连接条件及外连接中内表子节点下的连接条件)。
- 调用get_extra_condition_from_parent函数获取当前查询语句作为父查询的视图表时,父查询关于该视图的where,semi/anti join以及join条件(不包含外连接中外表子节点下的连接条件)。
- 将右连接转换为左连接,然后遍历from中的表,调用recursive_eliminate_outer_join_in_table_item函数递归地使用前两步获得的条件进行改写。
recursive_eliminate_outer_join_in_table_item函数在递归过程中,如果当前层的父节点连接都是内连接,则可以根据情况将当前表节点或者连接条件上拉至from和where条件中。另外,递归下一层时,如果子节点为当前层的内表(内连接的左右表及外连接的右表),需要将当前层的join条件合并到判断条件中。
对于每一层递归,函数的执行流程如下:
- 调用is_outer_joined_table_type函数判断当前表是否为需要关注的类型,即是否属于left join,full join和inner join中的一种。如果节点不属于上述类型(如基表或视图表)且满足前述上拉条件,则将当前表添加到临时from列表中,执行完成后上拉到查询语句的from列表。
- 调用do_eliminate_outer_join函数在当前层级执行连接转换。
do_eliminate_outer_join函数会根据当前节点的连接类型分别执行判断和改写,如下所示:
- 对于full join类型的节点,先执行一侧的判断和改写,然后交换左右子节点执行另一侧。如果满足改写条件,则将连接方式改为left join。执行另一侧时,如果满足当前改写条件且连接方式已经被改写为left join,则进一步改写为inner join;如果满足当前改写条件且连接方式仍为full join,则改写为left join;如果不满足当前改写条件,则不进行改写。除了改写为inner join的情况外,需要将左右节点交换复原。
- 对于left join类型的节点,如果满足改写条件,则将连接方式改为inner join。
can_be_eliminated函数负责根据当前条件判断连接是否能够被改写,执行流程如下:
- 调用can_be_eliminated_with_null_reject函数判断前述条件中是否存在右表的空值拒绝条件,如果存在则可以执行改写。
- 调用can_be_eliminated_with_foreign_primary_join函数判断左表是否通过外键依赖右表且连接条件仅包含外键的equal条件。如果外键中存在可能为空的列,则需要进一步判断该列对于前述条件(非连接条件)是否满足空值拒绝。如果上述条件都满足,则可以执行改写。
上述改写执行完成后,如果当前节点的连接方式为inner join类型且满足前述上拉条件,则会将连接条件上拉到查询语句的where条件中。如果不为inner join且满足上述上拉条件,则将当前节点添加到临时from列表中,执行完成后上拉到查询语句的from列表。