jjzjj

calcite物化视图基于规则查询改写原理解析

景达 2023-03-28 原文

1. 术语定义

物化视图:将视图的查询结果物化保存下来的结果。
物化视图 QueryRel: 生成物化视图的SQL关系表达式(查询语句)。
物化视图 TableRel:生成物化视图结果存储的关系表达式(存储物化视图的tableScan算子)。
COMPLETE : 查询表模型和物化视图表模型完全相同,比如查询引用了a,b,c三张表,物化视图也引用了a,b,c三张表。
VIEW_PARTIAL:查询表模型完全包含物化视图表模型,比如查询引用了a,b,c三张表,物化视图也引用了a,b两张表。
QUERY_PARTIAL: 物化试图表模型完全包含查询表模型,比如查询引用了a,b两张表,物化视图引用了a,b,c三张表。

2. 背景

物化视图指将SQL查询的结果保存下来。
查询使用物化视图改写是一种有效的加速方式,即将查询语句的全部或者部分改写成物化视图进行加速。
物化视图和查询完全等效可以直接命中查询,查询和物化视图不完全等效的情形下需要通过条件补偿,聚合上拉等方式,使用物化视图对于查询关系代数局部进行改写。

3. 问题定义

怎样通过基于规则的方式,使用物化视图对查询表达式进行局部改写?或者整体替换?

4. 概述

Calcite中基于规则UnifyRule查询改写的主要原理就是通过循环遍历查询SQL的RelNode关系表达式和生成物化视图QueryRelNode表达式,基于RelNode关系表达式命中对应的UnifyRule规则,如果匹配match UnifyRule规则,就调用对应规则的apply方法,使用物化结果的TableRel表达式对于查询SQL关系表达式进行改写。

5.流程图

5.1 结构关系示意图

在SubstitutionVistor中会使用UnifyRule,使用Target MutableNode 对于 Query MutableNode进行改写


结构关系示意图

5.2 视图替换流程图

图中相同的颜色表示相同的节点,视图替换核心流程示意图


物化视图匹配流程图.png

6.核心组件

UnifyRule:

使用物化视图target对查询关系表达式进行改写的规则,下面是源码到注释

  /** Rule that attempts to match a query relational expression
   * against a target relational expression.
   *
   * <p>The rule declares the query and target types; this allows the
   * engine to fire only a few rules in a given context.</p>
   */

UnifyRule子类如下

UnifyRule子类

SubstitutionVisitor:

替换查询关系表达式树的核心类,使用从下而上的替换算法,可以进行一定的改写和条件补偿,查询关系表达式和物化结果查询表达式不必完全相等

/**
 * Substitutes part of a tree of relational expressions with another tree.
 * <p>Uses a bottom-up matching algorithm. Nodes do not need to be identical.
 * At each level, returns the residue.</p>
 */

MutableRel

关系表达式RelNode在进行视图替换之前,会首先转换成MutableRel,之后使用MutableRel在SubstitutionVisitor中进行查询改写,当改写完成后,会把MutableRel再转成RelNode,它和RelNode是等价的,并且记录了它在父节点中的位置,便于视图替换的时候进行便利和回溯

/** Mutable equivalent of {@link RelNode}.
 *
 * <p>Each node has mutable state, and keeps track of its parent and position
 * within parent.
 */

核心方法

org.apache.calcite.plan.SubstitutionVisitor#go(org.apache.calcite.rel.RelNode)

7. 视图替换核心流程

7.1替换过程数据

这里选择一个聚合上拉的例子分析下基于规则视图改写机制

物化视图SQL语句

select C,D, count(A) from "@jingda".employees
GROUP BY C,D

物化视图查询关系表达式

  LogicalAggregate(group=[{0, 1}], EXPR$2=[COUNT($2)])
    LogicalProject(C=[$2], D=[$3], A=[$0])
      ScanCrel(table=["@jingda".employees], columns=[`A`, `B`, `C`, `D`, `E`, `F`], splits=[1])

物化视图结果存储算子

LogicalProject(C=[$0], D=[$1], EXPR$2=[CAST($2):BIGINT NOT NULL])
  ScanCrel(table=["__accelerator"."7db4b655-d381-4cc8-ba6f-adc2c40d0153"."479ce684-efd6-4420-8a5b-68350789b8bb"], columns=[`C`, `D`, `EXPR$2`], splits=[3])

查询语句SQL语句

select D, count(A) from "@jingda".employees
GROUP BY D

查询语句关系表达式

 LogicalAggregate(group=[{0}], EXPR$1=[COUNT($1)])
  LogicalProject(D=[$3], A=[$0])
    ScanCrel(table=["@jingda".employees], columns=[`A`, `B`, `C`, `D`, `E`, `F`], splits=[1])

改写后的SQL语句示意

select D, sum(A) FROM
(select C,D, count(A) from "@jingda".employees GROUP BY C,D)

改写后的关系表达式
这个地方可以看到,查询关系表达式已经使用了提前物化好的结果进行了改写

LogicalAggregate(group=[{1}], EXPR$1=[$SUM0($2)])
  LogicalProject(C=[$0], D=[$1], EXPR$2=[CAST($2):BIGINT NOT NULL])
    ScanCrel(table=["__accelerator"."7db4b655-d381-4cc8-ba6f-adc2c40d0153"."479ce684-efd6-4420-8a5b-68350789b8bb"], columns=[`C`, `D`, `EXPR$2`], splits=[3])

7.2 数据流转图

数据流转图.png

图中初始为Query为查询的SQL语句,Target为生成物化视图的SQL语句,Replacement为物化视图存储的位置算子

经过第一轮是命中了CalcToCalcUnifyRule规则,对于底层下面的关系表达式进行改写变成了

Calc(program: (expr#0..2=[{inputs}], D=[$t1], A=[$t2]))
 Calc(program: (expr#0..5=[{inputs}], C=[$t2], D=[$t3], A=[$t0]))
   Scan(table: [@rp_test, employees])

第二轮是命中了AggregateOnCalcToAggregateUnifyRule规则,对于底层下面的关系表达式进行改写变成了

Aggregate(groupSet: {1}, groupSets: [{1}], calls: [$SUM0($2)])
  Aggregate(groupSet: {0, 1}, groupSets: [{0, 1}], calls: [COUNT($2)])

最后把整个查询的关系表达式改写成

Holder
  Aggregate(groupSet: {1}, groupSets: [{1}], calls: [$SUM0($2)])
    Project(projects: [$0, $1, CAST($2):BIGINT NOT NULL])
      Scan(table: [__accelerator, 1c4b39df-c7c2-4e40-aebb-dfa87faa80a9, 14c0517b-10e6-4d66-92d3-f68e451c4216])

7.3 核心代码分析

核心的代码在org.apache.calcite.plan.SubstitutionVisitor#go(org.apache.calcite.rel.mutable.MutableRel)中

for (;;) {
      int count = 0;
      MutableRel queryDescendant = query;
    outer:
      while (queryDescendant != null) {
        for (Replacement r : attempted) {
          // 如果当前查询节点已经使用物化视图进行了替换,就搜索queryDescendant的另一个分支
          if (r.stopTrying && queryDescendant == r.after) {
            // This node has been replaced by previous iterations in the
            // hope to match its ancestors and stopTrying indicates
            // there's no need to be matched again.
            queryDescendant = MutableRels.preOrderTraverseNext(queryDescendant);
            continue outer;
          }
        }
        final MutableRel next = MutableRels.preOrderTraverseNext(queryDescendant);
        final MutableRel childOrNext =
            queryDescendant.getInputs().isEmpty()
                ? next : queryDescendant.getInputs().get(0);
        // 对于当前queryDescendant,遍历所有物化视图的关系表达式节点
        for (MutableRel targetDescendant : targetDescendants) {
         // 根据关系表达式节点获取可用的规则UnifyRule
          for (UnifyRule rule
              : applicableRules(queryDescendant, targetDescendant)) {
            UnifyRuleCall call =
                rule.match(this, queryDescendant, targetDescendant);
            if (call != null) {
              // 执行规则
              final UnifyResult result = rule.apply(call);
              if (result != null) {
                // 说明找到了匹配的物化视图,处理局部视图替换的逻辑
                ++count;
                attempted.add(
                    new Replacement(result.call.query, result.result, result.stopTrying));
                result.call.query.replaceInParent(result.result);

                // Replace previous equivalents with new equivalents, higher up
                // the tree.
                for (int i = 0; i < rule.slotCount; i++) {
                  Collection<MutableRel> equi = equivalents.get(slots[i]);
                  if (!equi.isEmpty()) {
                    equivalents.remove(slots[i], equi.iterator().next());
                  }
                }
                assert rowTypesAreEquivalent(result.result, result.call.query, Litmus.THROW);
                equivalents.put(result.result, result.call.query);
                // 如果待改写的节点等于物化视图结果,进行改写替换
                if (targetDescendant == target) {
                  // A real substitution happens. We purge the attempted
                  // replacement list and add them into substitution list.
                  // Meanwhile we stop matching the descendants and jump
                  // to the next subtree in pre-order traversal.
                  if (!target.equals(replacement)) {
                    Replacement r = replace(
                        query.getInput(), target, replacement.clone());
                    assert r != null
                        : rule + "should have returned a result containing the target.";
                    attempted.add(r);
                  }
                  substitutions.add(ImmutableList.copyOf(attempted));
                  attempted.clear();
                  queryDescendant = next;
                  continue outer;
                }
                // We will try walking the query tree all over again to see
                // if there can be any substitutions after the replacement
                // attempt.
                break outer;
              }
            }
          }
        }
        queryDescendant = childOrNext;
      }
      // Quit the entire loop if:
      // 1) we have walked the entire query tree with one or more successful
      //    substitutions, thus count != 0 && attempted.isEmpty();
      // 2) we have walked the entire query tree but have made no replacement
      //    attempt, thus count == 0 && attempted.isEmpty();
      // 3) we had done some replacement attempt in a previous walk, but in
      //    this one we have not found any potential matches or substitutions,
      //    thus count == 0 && !attempted.isEmpty().
      if (count == 0 || attempted.isEmpty()) {
        break;
      }
    }
    if (!attempted.isEmpty()) {
      // We had done some replacement attempt in the previous walk, but that
      // did not lead to any substitutions in this walk, so we need to recover
      // the replacement.
      undoReplacement(attempted);
    }
    return substitutions;

8. 查询改写技术总结

查询改写在业界大概的分类有三种技术

  • 基于结构信息改写
  • 基于规则视图替换
  • 基于语法改写
    本文介绍的是基于规则的视图替换技术,核心就是寻找查询关系表达式和物化视图表达式的相同视图,进行局部改写和替换,后面会介绍基于结构信息改写的技术特性,下面是三种技术的简单对比。
查询改写技术对比

原创不易,转载请注明出处,谢谢!

有关calcite物化视图基于规则查询改写原理解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  5. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

  6. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

    简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

  7. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  10. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

随机推荐