背景:
一个sql语句进入的执行过程:Parser->Binder->Planner->Optimizer->Executors几个阶段,最后生成一颗executor算子树。各个阶段的功能为:
Parser:根据sql语句生成一颗抽象语法树AST
Binder:根据数据库的元信息,讲抽象语法树转为一个具有库表信息的语义语法树
在得到 AST 后,Binder 将会把这个 AST 改写成 BusTub 可以理解的一个更高级的 AST,如:
Binder 会在 catalog 里面查 __mock_table_1
的信息,将 __mock_table_1
绑定到具体的实体表上 (table_oid=0
)。与此同时,将 select *
中的 *
展开成可以查到的所有列。
其中 SELECT
和 FROM
是关键字,*
和 __mock_table_1
是标识符。Binder 遍历 AST,将这些词语绑定到相应的实体上。
bustub> explain (binder) select * from __mock_table_1;
=== BINDER ===
BoundSelect {
table=BoundBaseTableRef { table=__mock_table_1, oid=0 },
columns=[__mock_table_1.colA, __mock_table_1.colB],
groupBy=[],
having=,
where=,
limit=,
offset=,
order_by=[],
is_distinct=false,
}
Planner:根据语义语法树生成查询计划
物理计划:定义具体的执行方式,例如数据库系统里的算子,hash join,走索引还是走扫描?根据具体数据情况选择具体的物理算子。
Planner 递归遍历 Binder 产生的 BusTub AST,产生一个初步的查询计划,查询计划也是一棵树的形式。在 BusTub 有很多种查询计划(Scan、Join、Projection......),每种查询计划都是这棵树上的一个节点。查询计划规定了数据的流向。数据从树叶流向树根,自底向上地流动,在根节点输出结果。
Optimizer:根据优化规则,重写等价的查询计划
Optimizer 主要有两种实现方式:
- 基于规则(Rule-based):该优化器按照硬编码在数据库中的一系列规则来决定SQL的执行计划,仅是根据预先定义好的规则优化 Plan。例如:谓词下推(Predicate Pushdown)、投影下推(Projection Pushdown)、列裁剪等
- 基于代价(Cost-based):估计多个查询计划的代价,进行比较,选择最小的代价。——前提是需要知道数据,知道数据量才知道时间。
bustub是基于规则的优化器,我们将不同的 Rule 按顺序应用到当前的执行计划上,产生最终的执行计划。比如下图利用谓词下推优化执行计划:
注意:在 Optimizer 生成的查询计划中,Join 会被优化成具体的 HashJoin 或 NestedIndexJoin 等。
Executors:根据查询计划,生成具体的算子执行树,具体的过程是,遍历查询计划树,将树上的 Plan 替换成对应的 Executor
我们通过火山模型执行算子,从叶子节点开始向上层算子吐自己的数据,直到顶层。
其中Executors是本节我们需要实现的对象
整体的示意图:
Catalog的介绍:
catalog在这一节中被广泛用到,我们必须要对它了解才能完成lab。
DBMS讲数据库的元数据存储在catalog中,如表名,索引,试图,用户权限等。一个数据库维护了内部的catalog来回答当前有哪些表存在,表中每一列的类型,位置在哪等等。
Schema
可以简单理解为对于字段和列的描述信息。如描述这个表中有哪些列,每个列的类型是什么。
对于一个tuple,每张表中的每一行数据是value+schema组成的。schema指明了tuple中value部分存储的是什么类型的数据,如value是100,schema指出是100元还是100米还是100分。
TableInfo
struct TableInfo {
/** The table schema */
Schema schema_;
/** The table name */
const std::string name_;
/** An owning pointer to the table heap */
std::unique_ptr<TableHeap> table_;
/** The table OID */
const table_oid_t oid_;
};
TableInfo 维护的是一张表的所有元数据(Metadata),比如一张表的 schema,表的名称,表的唯一标识 id,以及指向 table heap 的指针。
table heap 本身并不直接存储 tuple 数据,tuple 数据都存放在 table page 中。table heap 可能由多个 table page 组成,仅保存其第一个 table page 的 page id。需要访问某个 table page 时,通过 page id 经由 buffer pool 访问。
IndexInfo
struct IndexInfo {
/** The schema for the index key */
Schema key_schema_;
/** The name of the index */
std::string name_;
/** An owning pointer to the index */
std::unique_ptr<Index> index_;
/** The unique OID for the index */
index_oid_t index_oid_;
/** The name of the table on which the index is created */
std::string table_name_;
/** The size of the index key, in bytes */
const size_t key_size_;
};
类似的IndexInfo 维护的是一个索引表的所有元数据(Metadata),比如该索引 key 所在表的名称,索引名称、索引唯一表示 id 等信息。