cmu15445-project3 解析器与查询计划 实验总结
一、 实验内容
查询优化器是一个沟通语句SQL到具体执行逻辑的桥梁。
从query到具体执行,代码要经过如下的过程:
query parsing:从文本query得到一棵语法树,语法树等上下文被绑定(保存)到执行计划中
query optimization:从语法树生成优化的执行计划
query execution:按照执行计划调用执行器执行
底层执行:执行器调用事务管理器,执行并发控制,底层数据结构简化如project1,2
Parser是常规操作,用来生成语句AST;
Binder不重要,它只是用来让Planner拥有AST信息和数据库底层信息的;
Planner产生的Plan是一棵树,每个节点是一种操作;1条SQL语句可能产生很多个Plan,但是每个Plan只处理一个Table的数据
Optimizer是Project3的主体之一,它接收plan,输出优化后的plan。在Optimize接口中实现具体的逻辑。
Catalog图中未提及,保存了Table和Index的信息,正是一部分元数据存储的地方
背景知识:
通常流程:Executor调用Transaction接口,执行增删改查操作;对外暴露Next接口,,并加入标记;执行器的执行过程是迭代器模型,又称火山模型(Volcano)或者流水线(Pipeline)模型;
实现的接口
Task #1 - Access Method Executors 实现SeqScan、Insert、Delete、IndexScan 算子。
Task #2 - Aggregation & Join Executors:实现Aggregation、NestedLoopJoin、NestedIndexJoin 三个算子。HashJoin不是必须的要求。
Task #3 - Sort + Limit Executors and Top-N Optimization:实现Sort,Limit算子,并实现TopN算子优化;
Leaderboard Task:为 Optimizer 实现新的优化规则,让稍微复杂的样例sql 语句执行得更快。
二、 实验FAQ
1.迭代器模式
迭代器的优点是什么?执行过程可以不用一次性查出所有数据,节约内存。
对于查询,一次Next得到一条数据,而且在火山模型下,可能是嵌套的节点由上到下调用Next;
而对于删除,修改,一次调用要执行完对所有记录的操作。
其他模式:向量化模型(Vectorization Model),不遵循一次Next产生一条数据的模式。
2.执行器关联变量
每个executor有exec_ctx_,保存主要的上下文变量,包括事务管理器TransactionManager,目录Catalog,锁管理器;
executor中的table存储的方式:Catalog目录保存了TableInfo,TableInfo里保存了实际的Table;
一些特有的变量,如Aggregation算子用在group_by中,查询计划plan保存了group_bys_的查询条件;
Schema作为输出的变量,表示数据的每个列属性
3. HashJoin、NestedLoopJoin
JOIN为连接操作,即两个表的笛卡尔积,对于A JOIN B, 若A有n个满足条件的行,B有m个,若不加任何条件限制,则查询结果集为nxm;
NestedLoopJoin,HashJoin为数据库内部连接方式
而内连接,左外连接,右外连接为SQL支持的功能, 通过连接方式实现这些功能
1) 我们有常用结论,以较小的表作为左表进行连接,是一个代价相对小的方式,此时该表称为驱动表;
2) hash join(HJ)是一种用于equi-join(而anti-join就是使用NOT IN时的join)的技术。主要将小表、大表分别hash到桶中,最后比较哈希桶中的数据得到结果;
3) 当Join个数大于1个,如A JOIN B JOIN C的时候,即为嵌套查询;
4)大表、小表只是简化称呼,实际中可能操作的是临时表,是加了过滤条件的
4.执行计划优化
查询优化包括:
基于规则的优化(Rule-Based-Optimization,简称 RBO)
基于代价的优化(Cost-Based Optimization,简称 CBO)
找到OptimizeCustom,在其中多加一条实现自己的规则即可;
auto Optimizer::OptimizeCustom(const AbstractPlanNodeRef &plan) -> AbstractPlanNodeRef {
auto p = plan;
//所有的优化规则
p = OptimizeMergeProjection(p);
p = OptimizeMergeFilterNLJ(p);
p = OptimizeNLJAsIndexJoin(p);
// p = OptimizeNLJAsHashJoin(p); // Enable this rule after you have implemented hash join.
p = OptimizeOrderByAsIndexScan(p);
p = OptimizeSortLimitAsTopN(p);
return p;
}
Query 1: Where's the Index?
SELECT * FROM (t1 INNER JOIN t2 ON t1.x = t2.x) INNER JOIN t3 ON t2.y = t3.y;
其中表t1有索引,我们需要利用到索引加快查询速度,但是原有的查询优化只能利用右表的索引;对于此,可以根据索引的位置交换JOIN的顺序;
Query 2: Too Many Joins!
SELECT * FROM t4, t5, t6
WHERE (t4.x = t5.x) AND (t5.y = t6.y) AND (t4.y >= 1000000)
AND (t4.y < 1500000) AND (t6.x >= 100000) AND (t6.x < 150000);
可以看到Filter在JOIN的上方,要将其进行下推,提前进行Filter操作,避免处理过多的无用数据;具体做法是将Filter节点下移到JOIN的下方;
Query 3: The Mad Data Scientist
SELECT v, d1, d2 FROM (
SELECT v,
MAX(v1) AS d1, MIN(v1), MAX(v2), MIN(v2),
MAX(v1) + MIN(v1), MAX(v2) + MIN(v2),
MAX(v1) + MAX(v1) + MAX(v2) AS d2
FROM t7 LEFT JOIN (SELECT v4 FROM t8 WHERE 1 == 2) ON v < v4 GROUP BY v
)
这个查询里有很多重复的字段,未经优化会产生很多重复计算;
要进行列裁剪;包括将重复的字段进行合并;
5.测试
project3的测试主要是手工测试,通过写好的SQL在shell里执行,直接查看生成的数据是否正确;这里没有必要写成单测的形式,因为相较于前面的测试来说这个实验的代码确实略微简单;
用如下命令测试:
cd build && make -j$(nproc) shell
./bin/bustub-shell
make -j$(nproc) sqllogictest
./bin/bustub-sqllogictest ../test/sql/p3.00-primer.slt --verbose
References
[1]自动测评网站 GradeScope,course entry code: PXWVR5 https://www.gradescope.com/
[2] https://15445.courses.cs.cmu.edu/fall2022 课程官网
[3] https://github.com/cmu-db/bustub Bustub Github Repo
[4] Database System Concepts 6th version, Abraham.Silberschatz.