TiDB 源码学习:关于 Projection Pruning 的细节问题

简介: 查询优化器发现节点之间是 Proj/Aggr --> Proj 模式的时候(也就是某个 Proj 节点的祖先是 Proj或 Aggr 节点的时候),会考虑对子节点做 Projection Pruning 优化。是否可以消除 Proj 节点的判断依据是:当前的 Proj 节点输出的列是否和其子节点的输出列一样。如果一样,则可以消除。让我产生疑问的地方是判断输出列是否和子节点一样的代码逻辑,代码如下:func canProjectionBeEliminatedLoose(p *LogicalProjection) bool { for _, expr := range p.Ex

查询优化器发现节点之间是 Proj/Aggr --> Proj 模式的时候(也就是某个 Proj 节点的祖先是 Proj或 Aggr 节点的时候),会考虑对子节点做 Projection Pruning 优化。

是否可以消除 Proj 节点的判断依据是:当前的 Proj 节点输出的列是否和其子节点的输出列一样。如果一样,则可以消除。

让我产生疑问的地方是判断输出列是否和子节点一样的代码逻辑,代码如下:

func canProjectionBeEliminatedLoose(p *LogicalProjection) bool {

for _, expr := range p.Exprs {
    _, ok := expr.(*expression.Column)
    if !ok {
        return false
    }
}
return true

}
代码其实很简单,就是判断每个 Expr 是否是 *expression.Column 类型。根据 LogicalProjection (下面简称 LP)数据结构定义:

type LogicalProjection struct {

logicalSchemaProducer

Exprs []expression.Expression

// ...

}
LP 和其他一些逻辑查询节点类型一样,继承了 logicalSchemaProducer ,logicalSchemaProducer 保存列信息(参考 Schema 数据结构)。除此以外,LogicalProjection 还包含了 Exprs 字段。

我的疑问就是 要知道 Column 也是 Expression 类型,LogicalProjection 中 Exprs 字段和 schema.Columns 字段保存的信息不重复吗?二者有什么区别呢?

先说结论
对于很多简单查询来说 Exprs 和 schema.Columns 其实没啥区别,可以简单认为它们的信息就是一样的,比如下面这些查询:

select a from t;
select count(a) from t;
但是,对于 fields 中带有函数计算的查询语句(比如:select a + b as c from t),Exprs 字段中对应的数据类型就不再是 expression.Column 类型了, a+b 列也不是 LP 的子节点产生的,这类 LP 是不能被消除的。所以上面的代码逻辑简洁又正确。

具体代码分析
回顾代码中 LP 节点的创建过程,LP 是在 buildProjection 函数中创建出来的:

func (b PlanBuilder) buildProjection(p LogicalPlan, fields []ast.SelectField, mapper map[*ast.AggregateFuncExpr]int, considerWindow bool) (LogicalPlan, int, error) {

//...
proj := LogicalProjection{Exprs: make([]expression.Expression, , len(fields))}.Init(b.ctx)
schema := expression.NewSchema(make([]*expression.Column, , len(fields))...)
oldLen := 
for i, field := range fields {
    //...
    newExpr, np, err := b.rewrite(field.Expr, p, mapper, true)
    //...

    p = np
    proj.Exprs = append(proj.Exprs, newExpr)

    col, err := b.buildProjectionField(proj.id, schema.Len()+1, field, newExpr)
    //...
    schema.Append(col)
}
proj.SetSchema(schema)
proj.SetChildren(p)
return proj, oldLen, nil

}
代码中:

调用 rewrite 函数将 field.Expr 转换成 newExpr
将 newExpr 加入到 Exprs 字段中
调用 buildProjectionField 方法,使用 field/newExpr 创建新的列信息,加入到 schema 中
rewrite 函数是一个关键点,rewrite 过程涉及到了一些语法树和查询计划树中的数据结构,这里不做详细介绍。接下来,我们通过几个常见查询场景来说明一下 rewrite 函数起到的作用。

场景一:简单的查询语句

select a, b from t;
a 和 b 两个 field (*ast.SelectField)中的 Expr 类型都是 ast.ColumnNameExpr,rewrite 在处理这种类型的 field 时,其实就是找到它们的列名,从子节点的 schema 中找到对应的列(expression.Column 类型)并返回。

这类 field 在 rewrite 之后产生的 newExpr 是 *expression.Column 类型,这类语句中 LP 的 Exprs 和 schema.Columns 可以简单认为是一样的,没差别。

场景二:带有 Aggregation 的查询语句

select count(a) from t;
这种场景看似更复杂一些,其实 count(a) 这个 field 的 Expr 字段类型和QQ号码卖号场景一其实一样,也是 ast.ColumnNameExpr。rewrite 的处理过程基本没区别。

这个场景产生的查询计划是 DataSource -> LogicalAggregation -> LogicalProjection,count(a) 这一列其实是 LogicalAggregation 子节点提供的,LP 拿来用就行了。所以这类场景下,Exprs 和 schema.Columns 也没啥区别。

场景三:

select a + b as c from t;
在 fields 中有函数计算时,显然 LP 的子节点(在这里是 DataSource)的 schema 中是不可能包含这一列的。从这个场景下,Exprs 和 schema.Columns 就有所区别了。a+b field 中的 Expr 字段类型是ast.BinaryOperationExpr ,rewrite 的具体处理流程:

将 ast.BinaryOperationExpr 的 left 子节点和 right 子节点记录下来
根据 ast.BinaryOperationExpr 的函数类型找到对应的 ScalarFunction 类型
根据 step 1 和 step 2 中的信息,将该 field 转换成 expression.ScalarFunction 并返回
因此,a+b field 产生的 newExpr 并不是 Column 类型而是 ScalarFunction。

关于 rewrite

rewrite 函数在构建逻辑查询计划时作用很大,逻辑也比较复杂,不过在 Projection Pruning 优化中作用比较小,比较复杂的逻辑其实没有涉及到,特别是在上面提到的几个查询语句中。

目录
相关文章
|
6月前
|
分布式计算 分布式数据库 Spark
17张图带你彻底理解Hudi Upsert原理
17张图带你彻底理解Hudi Upsert原理
528 1
|
NoSQL Java
简析Cassandra的BATCH操作
cassandra中批量写入的操作称为batch,通过batch操作可以将多个写入请求合并为一个请求。这样有如下作用: 把多次更新操作合并为一次请求,减少客户端和服务端的网络交互。 batch中同一个partition key的操作具有隔离性。
6722 0
|
SQL 并行计算 Oracle
论文解读|从论文到工程实现:PolarDB Cost Based查询改写
论文解读|从论文到工程实现:PolarDB Cost Based查询改写
172 0
|
机器学习/深度学习 存储 并行计算
Meta 内部都在用的 FX 工具大起底:利用 Graph Transformation 优化 PyTorch 模型
Meta 内部都在用的 FX 工具大起底:利用 Graph Transformation 优化 PyTorch 模型
254 0
|
SQL 分布式计算 Java
扩展_Catalyst 优化器_优化过程 | 学习笔记
快速学习扩展_Catalyst 优化器_优化过程
179 0
扩展_Catalyst 优化器_优化过程 | 学习笔记
|
6月前
|
SQL 关系型数据库 数据库
ADBPG优化基础(一)ORCA优化器
AnalyticDB PostgreSQL(ADBPG)就是一堆并行的PostgreSQL?当然不是!ADBPG作为一个基于PostgreSQL的Massively Parallel Processing(MPP)全并行架构的分析型数据库,针对数据分析场景在很多方面得到了加强。如双优化器(GPORC...
ADBPG优化基础(一)ORCA优化器
|
SQL 分布式计算 Serverless
SPARK中的wholeStageCodegen全代码生成--以aggregate代码生成为例说起(6)
SPARK中的wholeStageCodegen全代码生成--以aggregate代码生成为例说起(6)
92 0
|
SQL 分布式计算 Spark
SPARK中的wholeStageCodegen全代码生成--以aggregate代码生成为例说起(9)
SPARK中的wholeStageCodegen全代码生成--以aggregate代码生成为例说起(9)
128 0
|
SQL 算法 数据挖掘
ML之Clustering之H-clustering:Hierarchical clustering算法相关论文、主要思路、关键步骤、代码实现等相关配图之详细攻略
ML之Clustering之H-clustering:Hierarchical clustering算法相关论文、主要思路、关键步骤、代码实现等相关配图之详细攻略