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 (
|
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
|
如果查询需要的列不属于索引,DoubleRead为true,此时代价乘2
if is.DoubleRead { |
如果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。