SQL编译优化原理

news2024/9/23 23:21:39

最近在团队的OLAP引擎上做了一些SQL编译优化的工作,整理到了语雀上,也顺便发在博客上了。SQL编译优化理论并不复杂,只需要掌握一些关系代数的基础就比较好理解;比较困难的在于reorder算法部分。

文章目录

  • 基础概念
    • 关系代数等价
  • join等价规则
    • 基数
    • join算法的成本
    • 查询问题的分类
    • 连接树的可能数量(搜索空间)
    • 查询图、join树和问题复杂度
  • Calcite概念
  • cascade/volcano
  • Calcite volcano递归优化器实现
  • Join reorder
    • 基于连接次序优化的动态规划算法
    • IKKBZ算法
    • bushy-tree
    • ASI
    • 归一化
  • Calcite实践
    • MultiJoinOptimizeBushyRule
  • Join 算法选择
  • 关联子查询优化
    • 为什么要消除关联子查询?
    • 基本消除规则
    • project和filter去关联化
    • Aggregate的去关联化
    • 集合运算的去关联化

基础概念

关系代数等价

参考《数据库系统概念》第七版
下面是第六版
在这里插入图片描述
在这里插入图片描述
注意自然连接和θ连接的交换律不能用于外连接

join等价规则

https://www.comp.nus.edu.sg/~chancy/sigmod18-reorder.pdf
在这里插入图片描述

在这里插入图片描述

基数

基数(cardinality)表示不同值的数量
在这里插入图片描述

join算法的成本

从上到下依次为嵌套循环连接、hash连接、排序合并连接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

ASI(相邻序列交换)

查询问题的分类

按照查询图:chain、cycle、star、clique
按照查询树结构:left-deep、zig-zag、bushy tree
按照join结构:有没有cross product
按照成本函数:有没有ASI属性

连接树的可能数量(搜索空间)

在这里插入图片描述

查询图、join树和问题复杂度

在这里插入图片描述
在这里插入图片描述

Calcite概念

RelNode:plan/subplan
relset:关系表达式等价的plan集合
relsubset :关系表达式和物理属性等价的plan集合
transformationRule:logical plan变化的规则集合
converterplan:将lp转化为pp的转化规则
RelOptRule :优化规则
RelOptNode 接口, 它代表的是能被 planner 操作的 expression node
statement:语句
reltrait 关系表达式特征
RelTraitDef 用于定义一类 RelTrait
RelTrait RelTrait是一个表示查询计划特征的抽象类。它用于描述查询计划的一些特性,是对应 TraitDef 的具体实例
Convention 是一种 RelTrait 用于表示 Rel 的调用约定(calling convention)
rexnode 行表达式
schema:逻辑模型
Program:一个SQL查询解析和优化的过程集合,可以将多个子过程组合在一起,以便进行SQL查询的解析和优化

cascade/volcano

volcano是top-down的模块化可剪枝sql优化模型。
volcano生成两个代数模型:logical and the physical algebras 分别优化lp和pp(pp主要选择执行算法)
一个volcano优化器必须提供如下部分:
(1) a set of logical operators,
(2) algebraic transformation rules, possibly with condition code,
(3) a set of algorithms and enforcers,
(4) implementation rules, possibly with condition code,
(5) an ADT “cost” with functions for basic arithmetic and comparison,
(6) an ADT “logical properties,”
(7) an ADT “physical property vector” including comparisons functions (equality and cover),
(8) an applicability function for each algorithm and enforcer,
(9) a cost function for each algorithm and enforcer,
(10) a property function for each operator, algorithm, and enf
volcano使用backward chaining的方式,只探索实际参与更大表达式的子查询和计划。这种方法可以避免对无关的子查询和计划进行搜索,从而提高查询优化的效率。

Calcite volcano递归优化器实现

RuleQueue 是一个优先队列,包含当前所有可行的 RuleMatch,findBestExpr() 时每次循环中我们从中取出优先级最高的并 apply,再根据 apply 的结果更新队列……如此往复,直到满足终止条件。
RuleQueue并没有使用大顶堆,仅仅保存了importance最大的节点。
我们想象我们现在有一组relnode,匹配上了很多RuleMatch,怎么决定先进行哪个match呢?
RuleMatch的importance决定了先进行哪个match,rulematch的importance定义为以下两个中较大的一个:

  1. 输入的 RelSubset 的 importance
  2. 输出的 RelSubset 的 importance
    RelSubset的importance又该如何定义?importance 定义为以下两个中比较大的一个:
    ● 该 RelSubset 本身的真实 importance
    ● 逻辑上相等的(即位于同一个 RelSet 中)任意一个 RelSubset 的真实 importance 除以 2
    真实importance的计算规则如下:
    在这里插入图片描述
    在这里插入图片描述

Join reorder

大部分的算法基于connectivity-heuristic,也就是说,只考虑equl-join

基于连接次序优化的动态规划算法

对于假设所有的连接都是自然连接的n个关系的集合,动态规划算法的复杂度为3^n
归并连接可以产生有序的结果,对于后面的排序可能有用(interesting sort order)。
目前我们用spark的连接算法,这条暂时没用。

IKKBZ算法

left-deep tree和bushy tree
left-deep tree 左深树
连接运算符的右侧输入都是具体关系,右子树必和左子树的节点之一有共享谓词。

左深树适合一般场景的优化。System R优化器只考虑左深树的优化,时间代价是n!,加入动态规划后,可以在n*2^n时间内找到最佳连接次序。

bushy-tree

适合多路连接和并行优化,但是很复杂。
不引入交叉乘的充要条件在于给定关系的父级必须已经得到。

ASI

简单来说就是等价谓词替换原则
定义rank函数:
在这里插入图片描述

成本函数
谓词的选择性指的是谓词对查询结果的过滤能力
对于join来说可以有如下定义:
在这里插入图片描述
在这里插入图片描述

我们将查询图视为一个有根树,我们说H的选择性指的是F和H之间的选择性。
行数和选择性之间则有如下关系(行数*选择性):
在这里插入图片描述
在这里插入图片描述

成本函数定义如下:
在这里插入图片描述

我们可以根据成本函数定义rank函数:
在这里插入图片描述

下面是对于ASI的证明:
在这里插入图片描述
在这里插入图片描述

归一化

在这里插入图片描述

Calcite实践

MultiJoinOptimizeBushyRule

第一部分进行初始化,unusedEdges存放join过滤条件(两个relnode之间)

final MultiJoin multiJoinRel = call.rel(0);
    final RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
    final RelBuilder relBuilder = call.builder();
    final RelMetadataQuery mq = call.getMetadataQuery();

    final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);

    final List<Vertex> vertexes = new ArrayList<>();
    int x = 0;
    for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
      final RelNode rel = multiJoin.getJoinFactor(i);
      double cost = mq.getRowCount(rel);
      vertexes.add(new LeafVertex(i, rel, cost, x));
      x += rel.getRowType().getFieldCount();
    }
    assert x == multiJoin.getNumTotalFields();

    final List<Edge> unusedEdges = new ArrayList<>();
    for (RexNode node : multiJoin.getJoinFilters()) {
      unusedEdges.add(multiJoin.createEdge(node));
    }

第二步选出成本(此处就是行数)差异最大的过滤条件
选一个行数较小的vertex作为majorFactor,另一个作为minorFactor

    // Comparator that chooses the best edge. A "good edge" is one that has
    // a large difference in the number of rows on LHS and RHS.
    final Comparator<LoptMultiJoin.Edge> edgeComparator =
        new Comparator<LoptMultiJoin.Edge>() {
          @Override public int compare(LoptMultiJoin.Edge e0, LoptMultiJoin.Edge e1) {
            return Double.compare(rowCountDiff(e0), rowCountDiff(e1));
          }

          private double rowCountDiff(LoptMultiJoin.Edge edge) {
            assert edge.factors.cardinality() == 2 : edge.factors;
            final int factor0 = edge.factors.nextSetBit(0);
            final int factor1 = edge.factors.nextSetBit(factor0 + 1);
            return Math.abs(vertexes.get(factor0).cost
                - vertexes.get(factor1).cost);
          }
        };

    final List<Edge> usedEdges = new ArrayList<>();
    for (;;) {
      final int edgeOrdinal = chooseBestEdge(unusedEdges, edgeComparator);
      if (pw != null) {
        trace(vertexes, unusedEdges, usedEdges, edgeOrdinal, pw);
      }
      final int[] factors;
      if (edgeOrdinal == -1) {
        // No more edges. Are there any un-joined vertexes?
        final Vertex lastVertex = Util.last(vertexes);
        final int z = lastVertex.factors.previousClearBit(lastVertex.id - 1);
        if (z < 0) {
          break;
        }
        factors = new int[] {z, lastVertex.id};
      } else {
        final LoptMultiJoin.Edge bestEdge = unusedEdges.get(edgeOrdinal);

        // For now, assume that the edge is between precisely two factors.
        // 1-factor conditions have probably been pushed down,
        // and 3-or-more-factor conditions are advanced. (TODO:)
        // Therefore, for now, the factors that are merged are exactly the
        // factors on this edge.
        assert bestEdge.factors.cardinality() == 2;
        factors = bestEdge.factors.toArray();
      }
      
      // Determine which factor is to be on the LHS of the join.
      final int majorFactor;
      final int minorFactor;
      if (vertexes.get(factors[0]).cost <= vertexes.get(factors[1]).cost) {
        majorFactor = factors[0];
        minorFactor = factors[1];
      } else {
        majorFactor = factors[1];
        minorFactor = factors[0];
      }
      final Vertex majorVertex = vertexes.get(majorFactor);
      final Vertex minorVertex = vertexes.get(minorFactor);

遍历unusedEdges,加入newFactors,对之前选出的majorVertex和minorVertex进行归一化并且加入vertexes

      // Find the join conditions. All conditions whose factors are now all in
      // the join can now be used.
      final int v = vertexes.size();
      final ImmutableBitSet newFactors =
          majorVertex.factors
              .rebuild()
              .addAll(minorVertex.factors)
              .set(v)
              .build();

      final List<RexNode> conditions = new ArrayList<>();
      final Iterator<LoptMultiJoin.Edge> edgeIterator = unusedEdges.iterator();
      while (edgeIterator.hasNext()) {
        LoptMultiJoin.Edge edge = edgeIterator.next();
        if (newFactors.contains(edge.factors)) {
          conditions.add(edge.condition);
          edgeIterator.remove();
          usedEdges.add(edge);
        }
      }
      double cost =
        majorVertex.cost
        * minorVertex.cost
        * RelMdUtil.guessSelectivity(
            RexUtil.composeConjunction(rexBuilder, conditions));
      final Vertex newVertex =
          new JoinVertex(v, majorFactor, minorFactor, newFactors,
              cost, ImmutableList.copyOf(conditions));
      vertexes.add(newVertex);

归一化之后进行选择性的重新计算,之后进入下一轮

// Re-compute selectivity of edges above the one just chosen.
      // Suppose that we just chose the edge between "product" (10k rows) and
      // "product_class" (10 rows).
      // Both of those vertices are now replaced by a new vertex "P-PC".
      // This vertex has fewer rows (1k rows) -- a fact that is critical to
      // decisions made later. (Hence "greedy" algorithm not "simple".)
      // The adjacent edges are modified.
      final ImmutableBitSet merged =
          ImmutableBitSet.of(minorFactor, majorFactor);
      for (int i = 0; i < unusedEdges.size(); i++) {
        final LoptMultiJoin.Edge edge = unusedEdges.get(i);
        if (edge.factors.intersects(merged)) {
          ImmutableBitSet newEdgeFactors =
              edge.factors
                  .rebuild()
                  .removeAll(newFactors)
                  .set(v)
                  .build();
          assert newEdgeFactors.cardinality() == 2;
          final LoptMultiJoin.Edge newEdge =
              new LoptMultiJoin.Edge(edge.condition, newEdgeFactors,
                  edge.columns);
          unusedEdges.set(i, newEdge);
        }
      }

最后一段,根据新的vertexes次序建立relnode节点

// We have a winner!
List<Pair<RelNode, TargetMapping>> relNodes = new ArrayList<>();
for (Vertex vertex : vertexes) {
  if (vertex instanceof LeafVertex) {
    LeafVertex leafVertex = (LeafVertex) vertex;
    final Mappings.TargetMapping mapping =
        Mappings.offsetSource(
            Mappings.createIdentity(
                leafVertex.rel.getRowType().getFieldCount()),
            leafVertex.fieldOffset,
            multiJoin.getNumTotalFields());
    relNodes.add(Pair.of(leafVertex.rel, mapping));
  } else {
    JoinVertex joinVertex = (JoinVertex) vertex;
    final Pair<RelNode, Mappings.TargetMapping> leftPair =
        relNodes.get(joinVertex.leftFactor);
    RelNode left = leftPair.left;
    final Mappings.TargetMapping leftMapping = leftPair.right;
    final Pair<RelNode, Mappings.TargetMapping> rightPair =
        relNodes.get(joinVertex.rightFactor);
    RelNode right = rightPair.left;
    final Mappings.TargetMapping rightMapping = rightPair.right;
    final Mappings.TargetMapping mapping =
        Mappings.merge(leftMapping,
            Mappings.offsetTarget(rightMapping,
                left.getRowType().getFieldCount()));
    if (pw != null) {
      pw.println("left: " + leftMapping);
      pw.println("right: " + rightMapping);
      pw.println("combined: " + mapping);
      pw.println();
    }
    final RexVisitor<RexNode> shuttle =
        new RexPermuteInputsShuttle(mapping, left, right);
    final RexNode condition =
        RexUtil.composeConjunction(rexBuilder, joinVertex.conditions);

    final RelNode join = relBuilder.push(left)
        .push(right)
        .join(JoinRelType.INNER, condition.accept(shuttle))
        .build();
    relNodes.add(Pair.of(join, mapping));
  }
  if (pw != null) {
    pw.println(Util.last(relNodes));
  }

Join 算法选择

关联子查询优化

我们将连接外部查询和子查询的算子叫做CorrelatedJoin(也被称之为lateral join, dependent join、apply算子等等。它的左子树我们称之为外部查询(input),右子树称之为子查询(subquery)。
在这里插入图片描述
在这里插入图片描述

注:bag语义,允许元素重复出现,和set语义正交

为什么要消除关联子查询?

在这里插入图片描述

CorrelatedJoin这个算子打破了以往对逻辑树自上而下的执行模式。普通的逻辑树都是从叶子节点往根结点执行的,但是CorreltedJoin的右子树会被带入左子树的行的值反复的执行。

基本消除规则

如果 Apply 的右边不包含来自左边的参数(或者只包含filter参数),那它就和直接 Join 是等价的
在这里插入图片描述
在这里插入图片描述

project和filter去关联化

尽可能把 Apply 往下推、把 Apply 下面的算子向上提。
在这里插入图片描述
在这里插入图片描述

Aggregate的去关联化

在这里插入图片描述

SELECT c_custkey
FROM CUSTOMER
WHERE 1000000 < (
    SELECT SUM(o_totalprice)
    FROM ORDERS
    WHERE o_custkey = c_custkey
)
// 等价于
select sum(p_price) > 1000000 from CUSTOMER.o_custkey left join ORDERS.c_custkey 
on CUSTOMER.o_custkey = ORDERS.c_custkey group by ORDERS.c_custkey

在这里插入图片描述

集合运算的去关联化

在这里插入图片描述
在这里插入图片描述

这一组规则很少能派上用场。在 TPC-H 的 Schema 下甚至很难写出一个带有 Union All 的、有意义的子查询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/806773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试 | 校招字符串相关高频算法题汇总【C++实现】

文章目录 1、反转字符串2、反转字符串||3、字符串最后一个单词的长度4、找字符串中第一个只出现一次的字符5、仅仅反转字母6、验证一个字符串是否是回文7、反转字符串中的单词【⭐】&#xff08;1&#xff09;移除给出字符串中的多余空格&#xff08;2&#xff09;反转整个字符…

Linux下安装VirtualBox虚拟机

1. 简介 VirtualBox是一款强大的x86和AMD64/Intel64虚拟化产品&#xff0c;适用于企业和家庭。VirtualBox不仅是为企业客户提供的一款功能丰富、高性能的产品&#xff0c;它也是根据GNU通用公共许可证(GPL)版本3条款作为开放源码软件免费提供的唯一专业解决方案。有关VirtualBo…

Modbus TCP使用例程

一、Modbus介绍 关于Modbus的介绍可参考前面的文章<modbus tcp协议介绍及分析>和<modbus rtu通信格式测试解析>这2篇文章。 二、Agile Modbus软件包介绍 Agile Modbus软件包的链接地址&#xff1a; https://gitee.com/RT-Thread-Mirror/agile_modbus Agile Modbus的…

Day46 算法记录| 动态规划 13(子序列)

这里写目录标题 300.最长递增子序列 674. 最长连续递增序列718. 最长重复子数组 300.最长递增子序列 视频解析&#xff1a; 第一层for循环遍历每一个元素&#xff0c; ------- 第二层for循环找到当前元素前面有几个小于该值的元素 结尾需要统计最多的个数 class Solution {pu…

如何有效地使用ChatGPT写小说讲故事?

​构思故事情节&#xff0c;虽有趣但耗时&#xff0c;容易陷入写作瓶颈。ChatGPT可提供灵感&#xff0c;帮你解决写作难题。要写出引人入胜的故事&#xff0c;关键在于抓住八个要素——主题、人物、视角、背景、情节、语气、冲突和解决办法。 直接给出故事模板&#xff0c;你可…

无线温湿度信息收集点模块的组成和工作状态及编程与组网建议

在传感技术与物联网的不断发展下&#xff0c;无线温湿度信息收集点模块作为一种重要的终端设备&#xff0c;被广泛应用于各个领域。本文将详细介绍该模块的组成和工作状态&#xff0c;并给出编程和组网的建议。 一、组成 该无线温湿度信息收集点模块由以下几个核心组成部分构成…

Banana Pi BPI-KVM – 基于 Rockchip RK3568 SoC 的 KVM over IP 解决方案

Banana Pi 已经开始开发基于 Rockchip RK3568 SoC 的 BPI-KVM 盒&#xff0c;但它不是迷你 PC&#xff0c;而是 KVM over IP 解决方案&#xff0c;旨在远程控制另一台计算机或设备&#xff0c;就像您在现场一样&#xff0c;例如能够打开和关闭连接的设备、访问 BIOS 等。 商业…

vagrant centos7 根目录扩容

目录 1 创建 centos7 虚拟机 2 扩容根目录 我知道的扩容方式有两种&#xff1a;1 直接扩容分区 &#xff1b;2 扩容逻辑卷。 我没找到为根目录设置到逻辑卷的方法&#xff0c;所以使用直接扩容分区。 1 创建 centos7 虚拟机&#xff0c; vagrant up vagrant ssh 查看磁盘…

Git-分支管理

文章目录 1.分支管理2.合并冲突3.合并模式4.补充 1.分支管理 Git分支管理是指在Git版本控制系统中&#xff0c;使用分支来管理项目的不同开发线路和并行开发的能力。通过分支&#xff0c;开发者可以在独立的环境中进行功能开发、bug修复等工作&#xff0c;而不会影响到主分支上…

文章详情页 - 评论功能的实现

目录 1. 准备工作 1.1 创建评论表 1.2 创建评论实体类 1.3 创建 mapper 层评论接口和对应的 xml 实现 1.4 准备评论的 service 层 1.5 准备评论的 controller 层 2. 总的初始化详情页 2.1 加载评论列表 2.1.1 实现前端代码 2.1.2 实现后端代码 2.2 查询当前登录用户的…

SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格

编辑&#xff1a;ll SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格 型号&#xff1a;SFP6012A 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247AC 恢复时间&#xff1a;100ns 正向电流&#xff1a;60A 反向耐压&#xff1a;1200V 芯片大小&#xff1a;102MIL*2…

苍穹外卖day-04

苍穹外卖day-04 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

一、window安装vagrant

篇章一、window安装vagrant 前言 在日常的学习中&#xff0c;需要在Window中学习Linux相关的操作命令&#xff0c;在本地熟悉Linux服务器环境&#xff0c;因此需要在电脑中安装Vagrant虚拟机来管理所需安装的Linux系统&#xff08;也就是后续的Centos-7&#xff09;。 1、下…

无涯教程-jQuery - Bounce方法函数

弹跳效果可以与effect()方法一起使用。这会在垂直或水平方向多次反弹元素。 Bounce - 语法 selector.effect( "bounce", {arguments}, speed ); 这是所有参数的描述- direction - 效果的方向。可以是"上(up)"&#xff0c;"下(down)"&#xf…

Debian9离线安装docker

1. 前言 在服务器禁止外网访问的情况下&#xff0c;无法通过apt-get install安装docker&#xff0c;使得docker安装变得异常曲折 本地下载安装包&#xff0c;scp到服务器通过dpkg -i 手动安装&#xff0c;启动docker服务失败… … 各种坑&#xff0c;猛男也要落泪 &#x1f92…

基于IAP的嵌入式系统在线编程设计(学习)

摘要&#xff1a;为了实现嵌入式系统程序的在线升级&#xff0c;提出一种基于IAP在线编程的程序更新方法。 以STM32L431控制器为例&#xff0c;该方法对控制器的片内FLASH进行区域划分&#xff0c;分别存放引导程序、执行程序及待更新程序。 系统通过运行引导程序将待更新程序…

飞行动力学-第15节-part2-松杆中性点 之 基础点摘要

飞行动力学-第15节-part2-松杆中性点 之 基础点摘要 1. 松杆中性点2. 松浮角2. 杆力梯度3. 参考资料 1. 松杆中性点 stick fixed&#xff1a; N 0 N_0 N0​&#xff0c;握杆&#xff0c;升降舵固定stick free&#xff1a; N 0 ′ N_0 N0′​&#xff0c;松杆&#xff0c;升降舵…

linux -网络编程一网络基本概念和Socket编程

目录 1 网络基础概念 1.1 协议 1.2分层模型 1.3 数据通信过程 1.4 网络应用程序的设计模式 1.5 以太网帧格式 1.6网络名词术语解析(自行阅读扫盲) 2 SOCKET编程 2.1 socket编程预备知识 2.2 socket编程主要的API函数介绍 目标&#xff1a; 了解OSI七层、TCP/IP四层模…

论文浅尝 | 预训练Transformer用于跨领域知识图谱补全

笔记整理&#xff1a;汪俊杰&#xff0c;浙江大学硕士&#xff0c;研究方向为知识图谱 链接&#xff1a;https://arxiv.org/pdf/2303.15682.pdf 动机 传统的直推式(tranductive)或者归纳式(inductive)的知识图谱补全(KGC)模型都关注于域内(in-domain)数据&#xff0c;而比较少关…

Centos7 安装man中文版手册

查找man中文安装包&#xff1a; yum search man-pages 安装man-pages-zh-CN.noarch: yum install -y man-pages-zh-CN.noarch