Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何使用calcite rule做SQL重写(上)

如何使用calcite rule做SQL重写(上)

作者头像
麒思妙想
发布于 2023-08-28 07:37:49
发布于 2023-08-28 07:37:49
1.8K00
代码可运行
举报
文章被收录于专栏:麒思妙想麒思妙想
运行总次数:0
代码可运行

各位读者朋友,我想死你们了,今天我带着 calcite这个专题的第三篇文章来了,今天我们来说说sql重写,这可能也是大家都有需求的方面,我计划这个专题分为三篇来写:

  • 上篇介绍 calcite 自带的 rule 做sql重写
  • 下篇介绍如何自定义 rule 来实现rewrite sql
  • 第三篇作为番外,不限于calcite,泛化倒使用 AST + Vistor,来完成真正意义上的SQL语句重写。那么我们就开始吧!Let's go!!!

对于 rewrite sql 这个需求,大家都会有各自得需求,从我的角度来看,主要分为:

  • 对象改写 简单的例如对Sql对象的替换 select a.firstname || a.lastname from a 作为输入,实际查询 select concat(b.first,b.last) from b
  • 语法转换 同源语义,但是由于数据库方言限制,select top 10 * from a 转换成 select * from a limit 10
  • 性能优化 一般会伴随语义和语法的转换,这里我们做等价代换的时候,还是要从关系代数的角度来证明规则的成立。在这里可能伴随着Sql语句得优化,也可能是对执行计划的优化。

下面我们以SQL优化为例,来看看calcite如何做。

SQL 优化

基于规则优化(RBO)

基于规则的优化器(Rule-Based Optimizer,RBO):根据优化规则对关系表达式进行转换,这里的转换是说一个关系表达式经过优化规则后会变成另外一个关系表达式,同时原有表达式会被裁剪掉,经过一系列转换后生成最终的执行计划。

RBO 中包含了一套有着严格顺序的优化规则,同样一条 SQL,无论读取的表中数据是怎么样的,最后生成的执行计划都是一样的。同时,在 RBO 中 SQL 写法的不同很有可能影响最终的执行计划,从而影响执行计划的性能。

基于成本优化(CBO)

基于代价的优化器(Cost-Based Optimizer,CBO):根据优化规则对关系表达式进行转换,这里的转换是说一个关系表达式经过优化规则后会生成另外一个关系表达式,同时原有表达式也会保留,经过一系列转换后会生成多个执行计划,然后 CBO 会根据统计信息和代价模型 (Cost Model) 计算每个执行计划的 Cost,从中挑选 Cost 最小的执行计划。

CBO = RBO + Cost Model + Model Iteration

由上可知,CBO 中有两个依赖:统计信息和代价模型。统计信息的准确与否、代价模型的合理与否都会影响 CBO 选择最优计划。从上述描述可知,CBO 是优于 RBO 的,原因是 RBO 是一种只认规则,对数据不敏感的呆板的优化器,而在实际过程中,数据往往是有变化的,通过 RBO 生成的执行计划很有可能不是最优的。事实上目前各大数据库和大数据计算引擎都倾向于使用 CBO,但是对于流式计算引擎来说,使用 CBO 还是有很大难度的,因为并不能提前预知数据量等信息,这会极大地影响优化效果,CBO 主要还是应用在离线的场景。

优化规则

无论是 RBO,还是 CBO 都包含了一系列优化规则,这些优化规则可以对关系表达式进行等价转换,常见的优化规则包含:

  • 谓词下推 Predicate Pushdown
  • 常量折叠 Constant Folding
  • 列裁剪 Column Pruning

谓词下推:

我们可能已经理解了什么是谓词下推,基本的意思predicate pushdown 是将SQL语句中的部分语句( predicates 谓词部分) 可以被 “pushed” 下推到数据源或者靠近数据源的部分。

对于Join(Inner Join)、Full outer Join,条件写在on后面,还是where后面,性能上面没有区别;

  • 对于Left outer Join ,右侧的表写在on后面、左侧的表写在where后面,性能上有提高;
  • 对于Right outer Join,左侧的表写在on后面、右侧的表写在where后面,性能上有提高;
  • 当条件分散在两个表时,谓词下推可按上述结论2和3自由组合;
  • 所谓下推,即谓词过滤在map端执行;所谓不下推,即谓词过滤在reduce端执行 注意:如果在表达式中含有不确定函数,整个表达式的谓词将不会被pushed

常量折叠

常量折叠也是常见的优化策略,这个比较简单,例如,有一个常量表达式 10 + 30,如果不进行常量折叠,那么每行数据都需要进行计算,进行常量折叠后的结果如下图所示( 对应 Calcite 中的 ReduceExpressionsRule.PROJECT_INSTANCE Rule)

列裁剪

列裁剪也是一个经典的优化规则,例如,一次查询并不需要扫描它的所有列值,而只需要列值 id,所以在扫描表之后需要将其他列进行裁剪,只留下列 id。这个优化带来的好处很明显,大幅度减少了网络 IO、内存数据量的消耗。

Calcite优化器

Calcite提供了两类型的优化器,即上述所说的RBO优化器和CBO优化器,在Calcite中的具体实现类对应HepPlanner(RBO)和VolcanoPlanner(CBO)。

HepPlanner优化器

HepPlanner简单理解就是两个循环,第一个循环会遍历用户提供的rule,第二个循环会遍历SQL树的节点,每当rule匹配到对应树节点的时候,会重新进行一遍循环。这个比较好理解。

VolcanoPlanner优化器

VolcanoPlanner则相对复杂一些,它不是简单地应用rule,而是会使用动态规划算法,计算每种rule匹配后生成新的SQL树的Cost信息,与原先SQL树的Cost信息相比较,如果新的树的Cost比较低,那么才会真正应用对应的rule。

案例

代码解析

首先,我们根据上一节的内容,来构建一个带条件的查询

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RelNode opTree = relBuilder
                .scan("consumers")
                .scan("orders")
                .join(JoinRelType.INNER,
                        relBuilder.call(SqlStdOperatorTable.EQUALS,
                                relBuilder.field("id"),
                                relBuilder.field("user_id")))
                .filter(
                        relBuilder.call(SqlStdOperatorTable.EQUALS,
                                relBuilder.field("lastname"),
                                relBuilder.literal("jacky")))
                .project(
                        relBuilder.field("id"),
                        relBuilder.field("goods"),
                        relBuilder.field("price"),
                        relBuilder.field("firstname"),
                        relBuilder.field("lastname"))
                .sortLimit(0, 5, relBuilder.field("id"))
                .build();

接下来,我们在优化器里加入条件下退规则,这里我们用到上文提到得 HepPlanner 也就是 RBO

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        HepProgramBuilder hepProgramBuilder = HepProgram.builder();
        hepProgramBuilder.addRuleInstance(FilterJoinRule.FilterIntoJoinRule.FilterIntoJoinRuleConfig.DEFAULT.toRule());
        HepProgram program = hepProgramBuilder.build();

        HepPlanner hepPlanner = new HepPlanner(program);
        hepPlanner.setRoot(opTree);
        RelNode r = hepPlanner.findBestExp();
  • 添加规则
  • 初始化 HepProgram 对象;
  • 初始化 HepPlanner 对象,并通过 setRoot() 方法将 RelNode 树转换成 HepPlanner 内部使用的 Graph;
  • 通过 findBestExp() 找到最优的 plan,规则的匹配都是在这里进行。

这里我们需要提一下,addRuleInstance 这个方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /**
   * Adds an instruction to attempt to match a specific rule object.
   *
   * <p>Note that when this method is used, it is NOT necessary to add the
   * rule to the planner via {@link RelOptPlanner#addRule}; the instance
   * supplied here will be used. However, adding the rule to the planner
   * redundantly is good form since other planners may require it.
   *
   * @param rule rule to fire
   */
  public HepProgramBuilder addRuleInstance(RelOptRule rule) {
    return addInstruction(new HepInstruction.RuleInstance(rule));
  }

在添加 RelOptRule 规则得时候,calcite 1.21 版本以后如何实例化规则,进行了修改,老版本使用 builder.addRuleInstance(FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN)

接下来打印一下执行计划,和查询结果就好了。完整DEMO代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

package com.dafei1288;


import org.apache.calcite.adapter.csv.CsvSchema;
import org.apache.calcite.adapter.csv.CsvTable;
import org.apache.calcite.plan.*;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.hep.HepProgram;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.externalize.RelWriterImpl;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.rel.rules.FilterJoinRule;
import org.apache.calcite.tools.RelRunners;


import java.io.File;
import java.io.PrintWriter;
import java.sql.ResultSet;


public class CalciteSqlRewriteCase {
    public static void main(String[] args) throws Exception {

        SchemaPlus rootSchema = Frameworks.createRootSchema(true);
        String csvPath = "src\\main\\resources\\db";
        CsvSchema csvSchema = new CsvSchema(new File(csvPath), CsvTable.Flavor.SCANNABLE);
        rootSchema.add("consumers", csvSchema.getTable("consumers"));
        rootSchema.add("orders", csvSchema.getTable("orders"));

        FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
                .parserConfig(SqlParser.Config.DEFAULT)
                .defaultSchema(rootSchema)
                .build();


        RelBuilder relBuilder = RelBuilder.create(frameworkConfig);

        RelNode cnode = relBuilder.scan("consumers").build();
        System.out.println("==> "+ RelOptUtil.toString(cnode));

        cnode = relBuilder.scan("consumers").project(relBuilder.field("firstname"),
                relBuilder.field("lastname")).build();
        System.out.println("==> "+RelOptUtil.toString(cnode));

        RelNode opTree = relBuilder
                .scan("consumers")
                .scan("orders")
                .join(JoinRelType.INNER,
                        relBuilder.call(SqlStdOperatorTable.EQUALS,
                                relBuilder.field("id"),
                                relBuilder.field("user_id")))
                .filter(
                        relBuilder.call(SqlStdOperatorTable.EQUALS,
                                relBuilder.field("lastname"),
                                relBuilder.literal("jacky")))
                .project(
                        relBuilder.field("id"),
                        relBuilder.field("goods"),
                        relBuilder.field("price"),
                        relBuilder.field("firstname"),
                        relBuilder.field("lastname"))
                .sortLimit(0, 5, relBuilder.field("id"))
                .build();

        RelWriter rw = new RelWriterImpl(new PrintWriter(System.out, true));
        opTree.explain(rw);


        System.out.println();
        System.out.println();
        System.out.println();


        HepProgramBuilder hepProgramBuilder = HepProgram.builder();
        hepProgramBuilder.addRuleInstance(FilterJoinRule.FilterIntoJoinRule.FilterIntoJoinRuleConfig.DEFAULT.toRule());
        HepProgram program = hepProgramBuilder.build();

        HepPlanner hepPlanner = new HepPlanner(program);
        hepPlanner.setRoot(opTree);
        RelNode r = hepPlanner.findBestExp();
        r.explain(rw);

        System.out.println();
        System.out.println();
        System.out.println();

        ResultSet result = RelRunners.run(r).executeQuery();
        int columns = result.getMetaData().getColumnCount();
        while (result.next()) {
            System.out.println(result.getString(1) + " " + result.getString(2));
        }
    }
}


结果展示

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
==> LogicalTableScan(table=[[consumers]])

==> LogicalProject(firstname=[$1], lastname=[$2])
  LogicalTableScan(table=[[consumers]])

8:LogicalSort(sort0=[$0], dir0=[ASC], fetch=[5])
  7:LogicalProject(id=[$0], goods=[$5], price=[$6], firstname=[$1], lastname=[$2])
    6:LogicalFilter(condition=[=($2, 'jacky')])
      5:LogicalJoin(condition=[=($0, $4)], joinType=[inner])
        3:LogicalTableScan(table=[[consumers]])
        4:LogicalTableScan(table=[[orders]])



17:LogicalSort(sort0=[$0], dir0=[ASC], fetch=[5])
  15:LogicalProject(id=[$0], goods=[$5], price=[$6], firstname=[$1], lastname=[$2])
    22:LogicalJoin(condition=[=($0, $4)], joinType=[inner])
      19:LogicalFilter(condition=[=($2, 'jacky')])
        3:LogicalTableScan(table=[[consumers]])
      4:LogicalTableScan(table=[[orders]])

1 book

Process finished with exit code 0

结果分析

可以看到我们的执行计划从

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
8:LogicalSort(sort0=[$0], dir0=[ASC], fetch=[5])
  7:LogicalProject(id=[$0], goods=[$5], price=[$6], firstname=[$1], lastname=[$2])
    6:LogicalFilter(condition=[=($2, 'jacky')])
      5:LogicalJoin(condition=[=($0, $4)], joinType=[inner])
        3:LogicalTableScan(table=[[consumers]])
        4:LogicalTableScan(table=[[orders]])

变成了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
17:LogicalSort(sort0=[$0], dir0=[ASC], fetch=[5])
  15:LogicalProject(id=[$0], goods=[$5], price=[$6], firstname=[$1], lastname=[$2])
    22:LogicalJoin(condition=[=($0, $4)], joinType=[inner])
      19:LogicalFilter(condition=[=($2, 'jacky')])
        3:LogicalTableScan(table=[[consumers]])
      4:LogicalTableScan(table=[[orders]])

也就实现了条件下推。

好了,上半部分我们就讲到这里,下一篇,我们来尝试自定义calcite的rule,来rewrite sql。

参考资料

https://zhuanlan.zhihu.com/p/61661909

https://github.com/tzolov/calcite-sql-rewriter/tree/master

https://guimy.tech/calcite/2021/01/02/introduction-to-apache-calcite.html

http://matt33.com/2019/03/17/apache-calcite-planner/

https://zhuanlan.zhihu.com/p/397365617

历史文章导读

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 麒思妙想 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何使用calcite rule做SQL重写(下)
上一篇文章我们介绍了如何使用默认规则做条件下推,今天我们来尝试自定义规则,来实现对SQL的重写。我们本期将会深入浅出的以修改查询表为例,进行Sql rewrite,这应该在我们湖仓一体的架构中,处于核心地位的需求。我们今天就深入浅出的来做一个案例 Select * from consumers 实际查询则为 Select * from consumers_1,这个需求在分库分表里应该也很常见。
麒思妙想
2023/09/12
1.2K1
如何使用calcite rule做SQL重写(下)
如何使用calcite构建SQL并执行查询
大家好,这是 Calcite 的第二篇文章了,我一直毫不掩饰对她的喜爱,而且一直在致力于为社区做一些贡献,如果你也喜欢这个项目的话,欢迎评论,转发,如果没看过第一篇的话,也欢迎移步去看看(手把手教你使用Calcite查看SQL执行计划)。如果你还不了解这个项目的话,我也希望能通过我,让你知道这个优秀的项目。
麒思妙想
2023/08/28
1.2K0
如何使用calcite构建SQL并执行查询
【Flink SQL】Apache Calcite 架构剖析
Apache Calcite 是一个动态的数据管理框架, 可以实现 SQL 的解析、验证、优化和执行。Calcite 是模块化和插件式的, 解析、验证、优化和执行的步骤都对应着一个相对独立的模块。用户可以选择使用其中的一个或多个模块,也可以对任意模型进行定制化扩展。
王知无-import_bigdata
2023/04/07
1K0
【Flink SQL】Apache Calcite 架构剖析
手把手教你使用Calcite查看SQL执行计划
大家好,我又腆着大脸来更新了,也是知道自己鸽了很久很久,也就不找说辞了,尽管确实是有点遭不住996了。还是恭祝大家端午安康吧,那么问题来了,粽子你是吃甜的?还是吃肉的呢?我先表个态,我吃肉的!
麒思妙想
2023/08/28
1K0
手把手教你使用Calcite查看SQL执行计划
Calcite系列(九):执行流程-优化器优化
优化器优化是SQL处理的第四步,也是最核心的一步,优化器优化本质是基于优化规则实现关系代数等价转换。
Yiwenwu
2024/04/22
1.1K0
Calcite系列(九):执行流程-优化器优化
calcite简单入门
Apache Calcite是一款开源的动态数据管理框架,它提供了标准的 SQL 语言、多种查询优化和连接各种数据源的能力,但不包括数据存储、处理数据的算法和存储元数据的存储库。
zhangheng
2020/04/29
8.1K0
SQL 数据库查询的优化工具及实用
本文主要是对数据库查询优化器的一个综述,包括查询优化器分类、查询优化器执行过程和CBO框架Calcite。
养码场
2018/09/18
1.7K0
SQL 数据库查询的优化工具及实用
Hive优化器原理与源码解析系列--优化规则HiveFilterAggregateTransposeRule(十八)
这篇文章来讲优化规则HiveFilterAggregateTransposeRule,主要功能是将Filter过滤器下推到Aggregate聚合操作之下。满足的前提条件,这些谓词表达式必须是确定性的。
用户7600169
2022/04/25
7430
Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)
目前,数据库优化器分两种,一种是基于规则优化器;另一种是基于成本优化器,这两种优化器各有千秋。但现在大部分成熟的数据库优化器都是两种优化器结合起来使用,这样做为了优化器在执行计划Plan的构建速度和准确性之间找到一个好的平衡点。
用户7600169
2022/04/25
5290
Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)
[源码分析] 带你梳理 Flink SQL / Table API内部执行流程
本文将简述Flink SQL / Table API的内部实现,为大家把 "从SQL语句到具体执行" 这个流程串起来。并且尽量多提供调用栈,这样大家在遇到问题时就知道应该从什么地方设置断点,对整体架构理解也能更加深入。
罗西的思考
2020/09/07
3.3K0
Apache Calcite 文档翻译 - 关系代数
关系代数是Calcite的核心。每个查询都被表示为一棵关系运算符的树。你可以将一条SQL语句翻译为关系代数,也可以直接建立树状结构。
tyrantlucifer
2022/04/19
1.4K0
Calcite系列(五):执行流程-概览
SQL执行流程有一套通用的步骤,尽管具体的实现可能会因数据库系统的不同而有所差异,但流程相对固定。以下是通用的SQL处理流程:
Yiwenwu
2024/04/18
4990
Hive优化器原理与源码解析系列--优化规则SortLimitPullUpConstantsRule(七)
这篇文章分享基于成本优化器CBO可插拔式优化规则SortLimitPullUpConstantsRule,从SQL角度讲,带有Order by 、 Where等值谓词常量条件的这种SQL语句写法中将谓词中上拉常量到Project投影(Select操作)中。
用户7600169
2022/04/25
7650
Hive优化器原理与源码解析系列--优化规则SortLimitPullUpConstantsRule(七)
Hive优化器原理与源码解析系列--优化规则UnionPullUpConstantsRule(八)
上篇文章讲解了SortLimitPullUpConstantsRule等值常量谓词上拉,这样可以把即出现在谓词中等于某个常量constant的又出现在Project投影中的变量或列引用,是此列引用不在参与中间结果的一系列的计算,直接在投影Project使用常量作为此列引用的返回值。在等价变换即输入结果和输出结果不变的前提下,达到优化的目的,这也是优化器的价值所在。
用户7600169
2022/04/25
5560
Apache Calcite 论文学习笔记
特别声明:本文来源于掘金,“预留”发表的[Apache Calcite 论文学习笔记](https://juejin.im/post/5d2ed6a96fb9a07eea32a6ff)
叁金
2019/07/22
1.5K0
Apache Calcite原理极简入门
Apache Calcite 是独立于存储与执行的SQL解析、优化引擎,广泛应用于各种离线、搜索、实时查询引擎,如Drill、Hive、Kylin、Solr、flink、Samza等。本文结合hive中基于代价的优化,解析calcite优化引擎的实现原理。
大数据真好玩
2020/06/07
2.5K0
【Flink】第二十九篇:源码分析 Blink Planner
【Flink】第四篇:【迷思】对update语义拆解D-、I+后造成update原子性丢失
章鱼carl
2022/03/31
2.2K0
【Flink】第二十九篇:源码分析 Blink Planner
Hive优化器原理与源码解析系列--优化规则HiveFilterSetOpTransposeRule(二十)
这篇文章来讲优化规则HiveFilterSetOpTransposeRule,主要功能是将Filter过滤器下推到SetOp集合操作之下,提前过滤掉不必要的数据,减少中间结果进行优化。
用户7600169
2022/04/25
6030
Hive优化器原理与源码解析系列--优化规则HiveFilterSetOpTransposeRule(二十)
Hive优化器原理与源码解析系列--优化规则AggregateProjectPullUpConstantsRule(十七)
这篇文章来讲优化规则AggregateProjectPullUpConstantsRule,顾名思义是将Aggregate汇总操作中常量字段上拉到Project投影操作中的优化规则,主要功能从Aggregate聚合中删除常量键。常量字段是使用RelMetadataQuery.getpulldupredicates(RelNode)推导的,其输入不一定必须是Project投影操作。但此Rule规则从不删除最后一列,简单来讲,如果groupBy字段只有一列,而且为常量,也不会执行此优化,因为聚合Aggregate([])返回1行,即使其输入为空。由于转换后的关系表达式必须与原始关系表达式匹配,为等价变换,因此常量被放置在简化聚合Aggregate上方的Project投影中。
用户7600169
2022/04/25
1.4K0
Hive优化器原理与源码解析系列--优化规则SortJoinReduceRule(二)
基于成本优化器CBO,常用的优化规则如子查询移除、相关性拆解、笛卡尔积加等值判断转换为内关联,谓词下推等等常用优化规则Rule。如谓词下推优化规则是将判断条件下推到数据源头,来加少中间结果,在成本优化器中,每个RelNode的中间结果大小即RowCount记录数大小决定一个RelNode的成本大小,(RowCount记录数是构成CostModel成本模型元素之一),此文讲述是HiveSort下推到HiveJoin下。也具有减少中间结果,降低一个RelNode关系表达式成本功能。在Hive中Sort操作符就代表在HQL中 SORT BY field LIMIT n 语句写法,上篇文章SortRemoveRule优化规则将由SortJoinReduceRule产生的SortLimit移除,详细可参考上篇文章Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)。
用户7600169
2022/04/25
5860
Hive优化器原理与源码解析系列--优化规则SortJoinReduceRule(二)
推荐阅读
相关推荐
如何使用calcite rule做SQL重写(下)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档