如何在Apache Calcite的帮助下创建物理(优化)树

362 阅读3分钟

在这篇文章中,我们将看到如何在Apache Calcite的帮助下为一个给定的查询创建物理(优化)树。

什么是Apache Calcite?

Apache Calcite是一个动态数据管理框架,包括SQL解析器、优化器、执行器和JDBC驱动。

Apache Calcite如何工作

假设我们有一个表,现在我们需要执行一个SQL查询。

val QUERY: String = """SELECT x from t
                        |""".stripMargin
    .replaceAll("\\n", " ")

但问题是查询中没有告诉数据库引擎如何获取数据的细节,所以Calcite就出现了。

Calcite为我们提供了一些步骤,在这些步骤的帮助下,你可以创建一个优化的树。

因此,让我们讨论一下所有的步骤

第1步:解析

整个过程从解析开始。一个查询,为了被数据库引擎理解,必须首先使用SQL解析器进行解析,该解析器接收一串字符,并试图以解析树的形式推断其语法结构。

例如,解析SQL SELECT语句的规则可能看起来像这样。

<SELECT> expressionList
           	[<FROM> table]
           	[<WHERE> condition]
           	[<GROUP> <BY> groupingList]
           	[<HAVING> condition]

第2步:验证

为了验证,我们在AbstractSchema类 的帮助下创建我们自己的模式。我们扩展Apache Calcite的AbstractSchema类来定义我们自己的模式。并对查询进行验证。

让我们来看看模式的样子:

public class SimpleSchema extends AbstractSchema {

    private final String schemaName;
    private final Map<String, Table> tableMap;

    private SimpleSchema(String schemaName, Map<String, Table> tableMap) {
        this.schemaName = schemaName;
        this.tableMap = tableMap;
    }

    @Override
    public Map<String, Table> getTableMap() {
        return tableMap;
    }
}

一旦验证通过,我们就可以得到SqlNode(SqlNode是一个SQL解析树。它可以是一个操作符,字面意思,标识符,等等)。

第3步:翻译成关系代数

为了将查询表示为树状结构,我们需要关系 代数**(关系运算符**)。

关系代数处理的是数据集的 抽象 转换,例如:

  • 选择: 基于一个谓词的过滤。
  • 投影: 选择和修改某一行的某些列。
  • 联合: 将几个行集合并为一个。
  • 聚合: 在一个行集上计算一个标量函数。

AST(抽象语法树)不便于查询优化,因为其节点的语义太复杂。

RelNode子类定义的关系运算符树上进行查询优化要方便得多,如Scan, Project, Filter, Join等。我们使用SqlToRelConverter,Apache Calcite的另一个巨大的类,将原始AST转换成关系树。

第4步:规划和优化

优化是一个将关系树转换为另一个关系树的过程。你可以用启发式或基于成本的计划器HepPlannerVolcanoPlanner分别进行基于规则的优化。

你也可以做任何没有规则的手动重写树。Apache Calcite带有几个强大的重写工具,如RelDecorrelatorRelFieldTrimme

我们将使用VolcanoPlanner来进行基于成本的优化。

现在我们使用我们的优化器来解析、验证和转换查询。

SqlNode sqlTree = optimizer.parse(sql);
SqlNode validatedSqlTree = optimizer.validate(sqlTree);
RelNode relTree = optimizer.convert(validatedSqlTree);

第5步:执行

产生的逻辑树看起来像这样:

LogicalAggregate(group=[{}], revenue=[SUM($0)]): rowcount = 1.0, cumulative cost = 63751.137500047684
  LogicalProject($f0=[*($1, $2)]): rowcount = 1875.0, cumulative cost = 63750.0
    LogicalFilter(condition=[AND(>=($3, ?0), <($3, ?1), >=($2, -(?2, 0.01)), <=($2, +(?3, 0.01)), <($0, ?4))]): rowcount = 1875.0, cumulative cost = 61875.0
      LogicalTableScan(table=[[tpch, lineitem]]): rowcount = 60000.0, cumulative cost = 60000.0

总结

Apache Calcite是一个灵活的查询优化框架。在这篇博文中,我们展示了如何使用Apache Calcite解析器、验证器、转换器和基于规则的优化器来优化SQL查询。