一、连接类型
这个应该不用多介绍,简单列举下
- 内连接: INNER JOIN
- 外连接: LEFT [OUTER] JOIN 、RIGHT [OUTER] JOIN、FULL [OUTER] JOIN
除此之外,GP还引入了三种特殊连接,用于提升子链接时使用
- SEMI JOIN:类似于IN的效果
- ANTI JOIN: 可以看成是LEFT JOIN的结果去除INNER JOIN的结果之后得到的结果
- JOIN_LASJ_NOTIN:用于NOT IN子链接提取
与外连接相比,SEMI JOIN和ANTI JOIN输出的结果集较少,对连接运算的结合律支持与内连接类似,更便于查询优化器的处理。
二、为什么要消除外连接
- 结果集更小
- 便于查询优化器优化,比如多个内连接可以改变连接顺序,寻找更好的查询计划,而非内连接不能随便交换连接顺序
如果能够将外连接转换为内连接,可以让优化器在查询优化的后续步骤更好的基于代价选择更优的连接顺序 。
三、如何消除外连接
- 试图将半外连接转换成内连接
- 试图将全外连接转换成办外连接或内连接
- 试图将LEFT JOIN转换成ANTI JOIN
3.1 外转内
先看一个简单连接查询
A LEFT JOIN B ON A.id = B.id WHERE B.id > 0
分析一下:
这个左外连接可以拆解成两部分
- 满足连接条件的部分,直接输出元组,相当于内连接的结果
- 不满足连接条件的部分,结果会将B的所有值填充为NULL。过滤条件
B.id > 0
求值自然也为NULL,所以这部分会被过滤条件(WHERE)全部过滤掉
所以上述查询等同于内连接的结果(v为A,B共有的任意字段):
A INNER JOIN B ON A.v = B.v WHERE B.v > 0
这种情况其实可以用一个词来总结:空值拒绝
这里需要理解一个概念:nonnullable_rels
如果当前表的所有属性都为NULL时会导致过滤表达式不为真(假或者NULL),满足该条件的表的集合称为nonnullable_rels。
对于nonnullable_rels的计算,还需要考虑递归的情况,将上一层的计算往一层传递。比如对于这个连接操作:
A LEFT JOIN (B RIGHT JOIN C ON B.v = C.v) ON A.id = C.id WHERE B.v > 10
当处理其中的子连接(B RIGHT JOIN C ON B.v = C.v)时,nonnullable_rels = {B},其来自于上一层的过滤条件。
所以这个子连接可以转换成内连接。
3.2 LEFT JOIN转ANTI JOIN
还是看一个例子
A LEFT JOIN B ON A.id = B.id AND B.v > 10 WHERE B.v IS NULL
过滤条件和链接条件是冲突的,对于连接条件不成立的部分,B的元组在连接结果会填充NULL。这部分在过滤条件中会被保留,连接条件成立的部分反而被过滤。所以上述查询可以转换成ANTI JOIN。
四、结语
实际实现时,Greenplum基于两阶段消除外连接,第一阶段通过函数reduce_outer_joins_pass1来完成标记,搜集所有的连接的信息并标记所有的外连接,第二阶段通过函数reduce_outer_joins_pass2实现真正的消除外连接操作,需要递归分别处理FromExpr子句和JoinExpr子句。
消除外连接对Greenplum查询优化器非常重要,不仅可以减少结果数据量,而且让优化器能够更好的基于代价调整连接顺序。