背景
是一个面向新的查询引擎,它提供了标准的SQL语言,多种查询和优化。之前叫做optiq,是作为Hive的SQL引擎,为hive提供成本规模的优化功能即CBO,(Cost Based ),2014年5月作为独立孵化项目开源。关于 的架构有三个特点:, , and ,就是灵活性、组件可插拔、可扩展,它的 SQL 层、 层等都可以单独使用,这也是 受总多开源框架欢迎的原因之一。
的架构
关于的架构,相对于传统数据库,他将数据存储和数据处理算法和元信息存储忽略了,这样使得更加兼容,适合统一查询服务。
由上图可以看到,最核心的就是优化器,一个 包含三个组成部分:
对SQL执行完整流程,分为4部分
的相关概念
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后的对象如图所示
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 优化
在中有两种优化器
这里看下 优化器的实现,是的基类,其子类实现如下图所示:
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技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。