Spark SQL中外链接查询中的谓词下推规则

SparkSqlsql

SparkSql是架构在spark计算框架之上的分布式Sql引擎,使用DataFrame和DataSet承载结构化和半结构化数据来实现数据复杂查询处理,提供的DSL能够直接使用scala语言完成sql查询,同时也使用thrift server提供服务化的Sql查询功能。SparkSql提供了Data Source API,用户经过这套API能够本身开发一套Connector,直接查询各种数据源,包括NoSql、RDBMS、搜索引擎以及HDFS等分布式FS上的文件等。和SparkSql相似的系统,从Sql和计算框架分离角度看应该就是Hive;从面相的业务类型看有PrestoDB、Impala等(均可以在必定程度上应对即系查询)。数据库

谓词下推

所谓谓词(predicate),英文定义是这样的:A predicate is a function that returns bool (or something that can be implicitly converted to bool),也就是返回值是true或者false的函数,使用过scala或者spark的同窗都知道有个filter方法,这个高阶函数传入的参数就是一个返回true或者false的函数。若是是在sql语言中,没有方法,只有表达式,where后边的表达式起的做用正是过滤的做用,而这部分语句被sql层解析处理后,在数据库内部正是以谓词的形式呈现的。网络

那么谓词为何要下推呢?说白了,这个问题就是要回答到底谁来完成过滤数据的操做。那么谁均可以来完成数据过滤呢?咱们大体能够把SparkSql中的查询处理流程作以下的划分: 架构


SparkSql首先会对输入的sql语句进行一系列的分析,包括词法解析(能够理解为搜索引擎中的分词这个过程)、语法分析以及语义分析(例如判断database或者table是否存在、group by必须和聚合函数结合等规则);以后是执行计划的生成,包括逻辑计划和物理计划,其中在逻辑计划阶段会有不少的优化,而物理计划则是RDD的DAG图的生成;这两步完成以后则是具体的执行了(也就是各类重量级的计算逻辑),这就会有各类物理操做符(RDD的Transformation)的乱入,和本文讨论的问题相关的则是Filter和Scan两个操做符。其中Scan操做符直接面向底层数据源,完成数据源的扫描读取;Filter操做符完成扫描后数据的过滤。app

咱们知道,能够经过封装SparkSql的Data Source API完成各种数据源的查询,那么若是底层数据源没法高效完成数据的过滤,就会执行直接的全局扫描,把每条相关的数据都交给SparkSql的Filter操做符完成过滤,虽然SparkSql使用的Code Generation技术极大的提升了数据过滤的效率,可是这个过程没法避免大量数据的磁盘读取,甚至在某些状况下会涉及网络IO(例如数据非本地化时);若是底层数据源在进行扫描时能很是快速的完成数据的过滤,那么就会把过滤交给底层数据源来完成,这就是SparkSql中的谓词下推(至于哪些数据源能高效完成数据的过滤以及SparkSql是又如何完成高效数据过滤的则不是本文讨论的重点)。框架

外链接查询和链接条件

外链接查询(outter join),分为左外链接查询、右外链接查询以及全外链接查询,全外链接使用的场景很少,因此本文重点讨论的是左链接查询和右链接查询。分布式

链接条件,则是指当这个条件知足时两表的两行数据才能”join“在一块儿被返回,例若有以下查询:函数

 
  1. SELECT LT.value, RT.value性能

  2. FROM lefttable LT LEFT JOIN righttable RT优化

  3.    ON LT.id = RT.id AND LT.id > 1

  4. WHERE RT.id > 2

其中的“LT.id=RT.id AND LT.id>1” 这部分条件被称为“join中条件”,直接用来判断被join的两表的两行记录可否被join在一块儿,若是不知足这个条件,两表的这两行记录并不是所有被踢出局,而是根据链接查询类型的不一样有不一样的处理,因此这并不是一个单表的过滤过程或者两个表的的“联合过滤”过程;而where后的“RT.id>2”这部分被称为“join后条件”,就是一个单表过滤过程。而上边提到的谓词下推可否在两类条件中使用,在SparkSql中则有特定的规则,以左外链接查询为例,规则以下:

左表join后条件下推

查询语句以下:

 
  1. SELECT LT.id, LT.value, RT.value

  2. FROM lefttable LT

  3. LEFT JOIN righttable RT

  4.    ON LT.id = RT.id

  5. WHERE LT.id > 1

来分析一下LT.id>1下推到左表进行数据过滤的结果,通过LT.id>1过滤后,左表变为:

  1. SELECT LT.id, LT.value, RT.value

  2. FROM (SELECT id, value

  3.    FROM lefttable LT

  4.    WHERE LT.id > 1

  5.    ) TT

  6. LEFT JOIN righttable RT

  7.    ON TT.id = RT.id

这是一个非相关子查询,即彻底能够先完成子查询,再完成父查询,子查询在查询过程当中和外部查询没有关联关系。

左表join中条件不下推

查询语句以下:

 
  1. SELECT LT.id, LT.value, RT.value

  2. FROM lefttable LT LEFT JOIN righttable RT

  3.    ON LT.id = RT.id AND LT.id > 1

谓词下推是为了提升查询效率,若是不下推也能够获得正确的查询结果,因此来看看不下推的状况下计算出的正确结果,join过程以下:

第一步:左表id为1的行在右表中能找到相等的id,可是左表的id为1,是不知足第二个join条件(LT.id>1)的,因此左表这一条至关于没有和右表join上,因此左表的值value保留,而右表的value为null(你没知足join中条件没join上还把你的值保留,给我搞个空值?没办法,就是这么任性)。

第二步:左表id为2的行在右表中能找到,并且左表id为2的行的id大于1,两个join条件都知足,因此算是和右表join上了,因此左表和右表的value都保留。最终的查询结果以下:

右表join中条件下推

查询语句以下:

 
  1. SELECT LT.id, LT.value, RT.value

  2. FROM lefttable LT LEFT JOIN righttable RT

  3.    ON LT.id = RT.id

  4. AND RT.id > 1

如今把RT.id>1这个右表join中条件下推,来过滤右表,过滤后以下:

可见,右表join中条件下推不下推,结果同样,因此,干嘛不下推?能够过滤掉一半的数据呢。Sparksql中的等价处理语句是:

 
  1. SELECT LT.id, LT.value, RT.value

  2. FROM LT LEFT JOIN (SELECT id, value

  3.    FROM righttable RT

  4.    WHERE RT.id > 1

  5.    ) TT

  6. ON LT.id = TT.id

右表join后条件不下推

这个应该是最违反常规理解的查询了,查询语句以下:

 
  1. SELECT LT.id, LT.value

  2. FROM lefttable LEFT JOIN righttable RT

  3.    ON LT.id = RT.id

  4. WHERE RT.id > 1

首先来看,join后条件不下推的状况,流程以下:

第一步:左表id为1的行在右表中能够找到,可是此时仅仅知足join条件,在使用where条件判断这条链接后数据时,发现右表的id不知足RT.id>1的条件,因此这条join结果不保留(注意,这里是不保留,全都不保留,左表右表都不保留,要跟上边的没join上,右表的值为null的状况区别开,这也是关键所在)

第二步:左表id为2的行和右表id为2的行join上了,同时也知足RT.id>1的where条件。 


很明显这实际上是一个错误的结果。

至此,左联接查询的四条规则分析完了,能够看出,在SparkSql中对于外链接查询时的过滤条件,并不能在全部状况下都用来进行数据源的过滤,若是使用得当会极大的提高查询性能,若是使用不当,则会产生错误的查询结果,而这种错误结果又不易发觉,因此使用时要格外当心。

推荐阅读:

1,SparkSql的优化器-Catalyst

2,SparkSql的Catalyst之图解简易版

3,实战phoenix



原文连接:https://blog.csdn.net/rlnLo2pNEfx9c/article/details/78975348