在上一节我们说明了不同查询树其对应的执行效率不一样。给定 sql 语句,sql 解释器会构造出不同的查询树,因此我们需要专门计算哪种查询树具有最优效率,在数据库系统中,专门负责此工作的模块叫规划器,本节我们研究该模块的实现。
首先我们先给出规划器的接口,在项目目录下创建新文件夹 planner,在里面添加文件 interface.go,然后实现代码如下:
package planner
import (
"record_manager"
)
type Plan interface {
Open() interface{
}
BlocksAccessed() int //对应 B(s)
RecordsOutput() int //对应 R(s)
DistinctValues(fldName string) int //对应 V(s,F)
Schema() record_manager.SchemaInterface
}
Plan 接口对象跟我们前面的 Scan 对象很像,不同在于 Scan 对象接入表的数据,而 Plan 接口对象接入表的 meta data 数据。在后面的实现中,我们会针对 Select, Project, Product 等关系代数运算去创建对应的 Plan 接口对象,下面我们先看第一个 Plan 实例的实现,创建文件 table_plan.go,实现代码如下:
package planner
import (
metadata_manager "metadata_management"
"query"
"record_manager"
"tx"
)
type TablePlan struct {
tx *tx.Transation
tblName string
layout *record_manager.Layout
si *metadata_manager.StatInfo
}
func NewTablePlan(tx *tx.Transation, tblName string, md *metadata_manager.MetaDataManager) *TablePlan {
tablePlanner := TablePlan{
tx: tx,
tblName: tblName,
}
tablePlanner.layout = md.GetLayout(tablePlanner.tblName, tablePlanner.tx)
tablePlanner.si = md.GetStatInfo(tblName, tablePlanner.layout, tx)
return &tablePlanner
}
func (t *TablePlan) Open() interface{
} {
return query.NewTableScan(t.tx, t.tblName, t.layout)
}
func (t *TablePlan) RecordsOutput() int {
return t.si.RecordsOutput()
}
func (t *TablePlan) BlocksAccessed() int {
return t.si.BlocksAccessed()
}
func (t *TablePlan) DistinctValues(tblName string) int {
return t.si.DistinctValues(tblName)
}
func (t *TablePlan) Schema() record_manager.SchemaInterface {
return t.layout.Schema()
}
Plan 的实现在结构上与我们前面说过的 Scan 一样,最底层是 TablePlan,他直接返回对应数据库表的统计信息,实现 SelectPlan, ProjectPlan, ProductPlan 的时候需要传入一个 Plan 接口对象,他们相关接口的调用会转向调用输入 Plan 对象的接口。其中较为复杂的是 SelectPlan 的实现,因为它的执行依赖于传入的 Predicate 对象,其中我们以前在 Predicate 对象中实现的 ReductionFactor 接口就会被用于 RecordsAccessed,以便估计查询条件执行后所返回的数据库表缩小的程度,接口 EquatesWithConstant 用于 DistinctValues 以便用于检测 Predicate 对象对应的查询是否是"A=c"这种类型,其中 A 是字段名,c 是常量。
以前我们为了调试方便,在 Predicate和 Term 类的实现中注释掉了 ReductionFactor ,现在我们回去将他们反注释回来,,对于这两个函数的逻辑,我们将在代码的调试演示视频中再解释一下,相关视频请在 B 站搜索” coding 迪斯尼“。下面我们看 SelectPlan 的实现,创建文件 select_plan.go 实现代码如下:
package planner
import (
"query"
"record_manager"
)
type SelectPlan struct {
p Plan
pred *query.Predicate
}
func NewSelectPlan(p Plan, pred *query.Predicate) *SelectPlan {
return &SelectPlan{
p: p,
pred: pred,
}
}
func (s *SelectPlan) Open() interface{
} {
scan := s.p.Open()
return query.NewSelectionScan(scan.(query.UpdateScan), s.pred)
}
func (s *SelectPlan) BlocksAccessed() int {
return s.p.BlocksAccessed()
}
func (s *SelectPlan) RecordsOutput() int {
return s.p.RecordsOutput() / s.pred.ReductionFactor(s.p)
}
func (s *SelectPlan) min(a int, b int) int {
if a <= b {
return a
}
return b
}
func (s *SelectPlan) DistinctValues(fldName string) int {
if s.pred.EquatesWithConstant(fldName