背景

是一个面向新的查询引擎,它提供了标准的SQL语言,多种查询和优化。之前叫做optiq,是作为Hive的SQL引擎,为hive提供成本规模的优化功能即CBO,(Cost Based ),2014年5月作为独立孵化项目开源。关于 的架构有三个特点:, , and ,就是灵活性、组件可插拔、可扩展,它的 SQL 层、 层等都可以单独使用,这也是 受总多开源框架欢迎的原因之一。

ast树_ast树_ast树

的架构

关于的架构,相对于传统数据库,他将数据存储和数据处理算法和元信息存储忽略了,这样使得更加兼容,适合统一查询服务。

ast树_ast树_ast树

由上图可以看到,最核心的就是优化器,一个 包含三个组成部分:

对SQL执行完整流程,分为4部分

ast树_ast树_ast树

的相关概念

3.1

public interface Schema {
  Table getTable(String name);
  Set getTableNames();
  RelProtoDataType getType(String name);
  Set getTypeNames();
  Collection getFunctions(String name);
  Set getFunctionNames();
  Schema getSubSchema(String name);
  Set getSubSchemaNames();
  Expression getExpression(SchemaPlus parentSchema, String name);
  boolean isMutable();
  Schema snapshot(SchemaVersion version);
  /** Table type. */
  enum TableType {}
}

Table

public interface Table {
  RelDataType getRowType(RelDataTypeFactory typeFactory);
  Statistic getStatistic();
  Schema.TableType getJdbcTableType();
  boolean isRolledUp(String column);
  boolean rolledUpColumnValidInsideAgg(String column, SqlCall call,
      SqlNode parent, CalciteConnectionConfig config);
}

代表表的数据定义,对应表的数据列名和类型。

3.2 SQL Parse(SQL -> SQL Node)

使用作为SQL解析,通过中定义的Parse.jj文件,生成一系列的java代码,生成的代码会把SQL转换成AST的数据结构(SQL Node类型)

与 相似的工具还有 ANTLR, 中的 jj 文件也跟 ANTLR 中的 G4文件类似, Spark 中使用这个工具做类似的事情。

要实现一个SQL ,他的功能有两点,这里都需要在 jj 文件中定义

设计词法和语义,定义SQL中的具体元素。实现词法分析器(Lexer)和语法分析器(Parse),完成对SQL的解析,完成相应的转换。

//org.apache.calcite.prepare.CalcitePrepareImpl
//... 
//解析sql->SqlNode
SqlParser parser = createParser(query.sql,  parserConfig);
      SqlNode sqlNode;
      try {
        sqlNode = parser.parseStmt();
        statementType = getStatementType(sqlNode.getKind());
      } catch (SqlParseException e) {
        throw new RuntimeException(
            "parse failed: " + e.getMessage(), e);
      }
//...

经过上述代码sql经过.解析之后,会生成一个,Debug后的对象如图所示

ast树_ast树_ast树

3.3 验证和转换 (SQL Node -> )

经过上面的解析,会将SQL翻译成SQL 的抽象语法树 AST,SQL Node就是抽象语法树的节点Node,而Rel代表关系表达式( ),所以从sql node -> rel node 的过程,是一个转换,一个校验的过程。

//org.apache.calcite.prepare. Prepare
//function prepareSql
RelRoot root =
        sqlToRelConverter.convertQuery(sqlQuery, needsValidation, true);

3.3.1 校验

语法检查前我们要先知道元信息,这个检查会包括表名,字段名ast树,字段类型,函数名,进行语法检查的实现如下:

//org.apache.calcite.sql.validate.SqlValidatorImpl
 public SqlNode validate(SqlNode topNode) {
    SqlValidatorScope scope = new EmptyScope(this);
    scope = new CatalogScope(scope, ImmutableList.of("CATALOG"));
    final SqlNode topNode2 = validateScopedExpression(topNode, scope);
    final RelDataType type = getValidatedNodeType(topNode2);
    Util.discard(type);
    return topNode2;
  }

在对SQL node的校验过程中,会对AST的语法进行补充,例如:

//校验前
select province,avg(cast(age as double)) from t_user group by province
//校验后
SELECT `T_USER`.`PROVINCE`, AVG(CAST(`T_USER`.`AGE` AS DOUBLE))
FROM `TEST_PRO`.`T_USER` AS `T_USER`
GROUP BY `T_USER`.`PROVINCE`

3.3.2 SQL Node->Rel Node

在经过上面进行合法的补充了一定语义的AST树之后,而中已经准备好各种映射信息后ast树,就开始具体的的转换了。

//org.apache.calcite.sql2rel.SqlToRelConverter
 protected RelRoot convertQueryRecursive(SqlNode query, boolean top,
      RelDataType targetRowType) {
    final SqlKind kind = query.getKind();
    switch (kind) {
    case SELECT:
      return RelRoot.of(convertSelect((SqlSelect) query, top), kind);
    case INSERT:
      return RelRoot.of(convertInsert((SqlInsert) query), kind);
    case DELETE:
      return RelRoot.of(convertDelete((SqlDelete) query), kind);
    case UPDATE:
      return RelRoot.of(convertUpdate((SqlUpdate) query), kind);
    case MERGE:
      return RelRoot.of(convertMerge((SqlMerge) query), kind);
    case UNION:
    case INTERSECT:
    case EXCEPT:
      return RelRoot.of(convertSetOp((SqlCall) query), kind);
    case WITH:
      return convertWith((SqlWith) query, top);
    case VALUES:
      return RelRoot.of(convertValues((SqlCall) query, targetRowType), kind);
    default:
      throw new AssertionError("not a query: " + query);
    }
  }

测试SQL

select province,avg(cast(age as double)) from t_user group by province

得到的语法树

Plan after converting SqlNode to RelNode
LogicalAggregate(group=[{0}], EXPR$1=[AVG($1)])
  LogicalProject(PROVINCE=[$3], $f1=[CAST($2):DOUBLE])
    LogicalTableScan(table=[[TEST_PRO, T_USER]])

3.4 优化

在中有两种优化器

这里看下 优化器的实现,是的基类,其子类实现如下图所示:

ast树_ast树_ast树

3.4.1 基于规则的优化(RBO)

基于规则的优化器(Rule-Based ,RBO):根据优化规则对关系表达式进行转换,这里说的是,一个表达式A经过优化 得到另一个表达式B,得到B的同时A被裁掉,经过一系列转换得到最终的结果。

RBO有着一套严格顺序的优化规则,同样一条SQL,无论读取表中数据是什么样的,最后生成的执行计划都是相同的,所以SQL的性能可能和SQL本身的书写有很大的关系。

3.4.2 基于代价的优化 (CBO)

基于代价的优化器(Cost-Based ,CBO):也是根据优化规则对表达式进行转换,这里不同的是,表达式A在转换表达式B后,会被保存下来,根据不同的规则生成多个不同的表达式,CBO会根据统计模型和代价模型,在多个表达式选择一个cost最小的计划作为整个执行计划。

由上可知,CBO有两个依赖,一个是统计模型,一个是代价模型,目前大多数的计算引擎都趋向CBO,但是CBO比较有难度,因为不能明确预知数据量大小,所以CBO主要用于离线的场景。

感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将坚持持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注