分布式数据库TiDB中优化器相关代码

TiDB中优化器有关代码

 

TiDB的优化分为基于规则的优化(Rule Based Optimization)和基于代价的优化(Cost Based Optimization)。TiDB 的优化器相关代码在 plan 包中,这个包的主要工作是将 AST 转换为查询计划树,树中的节点是各种逻辑算子或者是物理算子,对查询计划化的各种优化都是通过调用树根节点的各种方法,递归地对所有节点进行优化,并且会不断的对树中的节点进行转换和裁剪。

 

最重要的几个接口在 plan.go 中,包括:

Plan: 所有查询计划的接口

LogicalPlan:逻辑查询计划,所有的逻辑算子都需要实现这个接口

PhysicalPlan:物理查询计划,所有的物理算子都需要实现这个接口

逻辑优化的入口是 planbuilder.build(),输入是 AST,输出是逻辑查询计划树。然后在这棵树上进行逻辑查询优化:

    调用 LogicalPlan PredicatePushDown 接口,将谓词尽可能下推

    调用 LogicalPlan PruneColumns 接口,将不需要的列裁减掉

    调用 aggPushDownSolver.aggPushDown,将聚合算子下推到 Join 之前

拿到逻辑优化后的查询计划树之后,会进行物理优化,代码的入口是对逻辑查询计划树的根节点调用 convert2PhysicalPlan(&requiredProperty{}),其中 requiredProperty 是对下层返回结果顺序、行数的要求。

 

逻辑查询计划树从根节点开始,不断的递归调用,将每个节点从逻辑算子转成物理算子,并且根据每个节点的查询代价找到一条比较好的查询路径。

 

l Plan包下的一些代码

 

physical_plan_builder.go根据逻辑查询计划制定出物理查询计划,其中定义了一些常量作为查询代价计算的一些权重

const (
   netWorkFactor      = 1.5
   netWorkStartFactor = 20.0
   scanFactor         = 2.0
   descScanFactor     = 5 * scanFactor
   memoryFactor       
= 5.0
   hashAggMemFactor   = 2.0
   selectionFactor    = 0.8
   distinctFactor     = 0.8
   cpuFactor          = 0.9
   aggFactor          = 0.1

   joinFactor         = 0.3
)

 

 

 

PhysicalPlan是一个接口,其中有一个函数为matchProperty,如果一个物理计划匹配所需的属性则用matchProperty计算其代价。matchProperty通常在convert2PhysicalPlan的结尾被调用。有一些物理计划没有实现matchProperty是因为没有属性用来匹配,这些物理计划将直接计算代价。如果一个物理计划没有匹配到所需的属性,则将其代价置为MaxInt64,这样它将不会被选为最后的物理计划。

 

 match_property.go中大部分函数实现了接口方法matchProperty

 

1

func (ts *PhysicalTableScan) matchProperty(prop *requiredProperty, infos ...*physicalPlanInfo) *physicalPlanInfo

PhysicalTableScan代表一个表扫描计划。首先查询代价为

cost := rowCount * netWorkFactor

如果有limit(即SQL语句中有limit)则为

cost = float64(prop.limit.Count+prop.limit.Offset) * netWorkFactor

如果requiredProperty中数组props长度为0(即所需属性为0),代价即为当前代价,计算结束。

否则,继续进行判断。如果requiredProperty中数组props长度为1且该属性为主键并且包含filter条件的话,代价则为

cost += rowCount * cpuFactor

然后计算结束。

否则,继续进行判断。如果此时有limit,进行addTopN操作,操作成功代价为

cost += rowCount * cpuFactor

操作失败代价为

cost = rowCount * netWorkFactor

然后计算结束。

如果以上条件都不满足,则代价为MaxFloat64。

 

2)

func (is *PhysicalIndexScan) matchProperty(prop *requiredProperty, infos ...*physicalPlanInfo) *physicalPlanInfo

PhysicalIndexScan代表一个索引扫描计划

如果有limit,代价则为limit的数量乘以netWorkFactor,否则为行数乘以netWorkFactor

rowCount := infos[0].count
if prop.limit != nil {
   rowCount = float64(prop.limit.Count)
}
cost := rowCount * netWorkFactor

 

如果查询需要的列不属于索引,DoubleRead为true,此时代价乘2

if is.DoubleRead {
   cost *= 2
}

如果requiredProperty中数组props长度为0(即所需属性为0),代价即为当前代价,计算结束。

否则继续,如果所有列都能匹配上,代价为

sortedCost := cost + rowCount*cpuFactor

计算结束。

否则继续,当前判断是否有limit,如果有且执行addTopN成功的话,代价为

cost += infos[0].count * cpuFactor

执行失败代价为

cost = infos[0].count * netWorkFactor

计算结束。

如果以上条件都不满足,则代价为MaxFloat64。

3)

func (p *PhysicalHashSemiJoin) matchProperty(_ *requiredProperty, childPlanInfo ...*physicalPlanInfo) *physicalPlanInfo

PhysicalHashSemiJoin代表一个用于semi join的hash join,该物理计划代价为两个子计划代价的和。

4

func (p *PhysicalMergeJoin) matchProperty(prop *requiredProperty, childPlanInfo ...*physicalPlanInfo) *physicalPlanInfo

PhysicalMergeJoin代表一个用于inner/outer join的merge join,该物理计划代价为两个子计划代价的和。

5

func (p *PhysicalHashJoin) matchProperty(prop *requiredProperty, childPlanInfo ...*physicalPlanInfo) *physicalPlanInfo

PhysicalHashJoin代表一个用于inner/outer join的hash join,代价为两个子计划代价的和,如果右边是小表的话则代价为

   cost += lCount + memoryFactor*rCount

否则代价为

   cost += rCount + memoryFactor*lCount

6)

func (p *Union) matchProperty(_ *requiredProperty, childPlanInfo ...*physicalPlanInfo) *physicalPlanInfo

Union代表一个Union计划,其代价为所有子计划的代价和。

 

今年5-8月,TiDB团队开发了新的基于代价的优化(Cost Based Optimization


ps:这篇文章是之前好久就已经写好了,其中大部分资料来自TiDB官网,代码来自于github。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值