ByteYoungDB实践|青训营笔记

375 阅读4分钟

这是我参与「第三届青训营-后端场」笔记创作活动的第21篇笔记。

0. 课前准备

SQL解析器开源项目: github.com/hyrise/sql-…

项目地址: github.com/thuyy/ByteY…

1. 整体结构

sql-parser

hyrise/sql-parser/src/parser/flex_lexer.I 词法解析器,将关键词转化为token流

hyrise/sql-parser/src/parser/bison_parser.y 语法解析。枚举不用类别的statement语法树。

hyrise/sql-parser/src/sql/SQLStatement.h 语法树基类

BYTE YOUNG DB

语义解析,将语法树与数据库元信息进行对比检验

Optimizer:根据产生的查询树,生成对应的计划树。计划树由各个基础算子组成。

Executor:使用火山模型。依赖计划树生成对应的执行树,每一个PlanNode对应一个Operator。

存储引擎

因为是内存态数据库,所以数据结构可以设计的比较简单。每次申请一批内存,可以降低内存碎片化的问题,提高内存访问效率。

事务引擎

不考虑并发情况以及磁盘持久化。所以没有MVCC机制,只能够实现事务的提交和回滚。

使用一个undo栈,每次更新一行数据,把老版本push进undo stack。如果事务回滚,就逐步pop栈。

2.代码介绍

.clang-format:代码格式信息。使用clangformat工具,规范代码格式。

main

死循环读入用户命令,(使用execstmt函数)执行命令。

execstmt函数:里面包含了Parser、Optimizer、Executor三个结构。Parser进行词法解析、语法解析、语义分析,最后生成一个Parser的结构。将Parser结构的每一个statement输入Optimizer,Optimizer生成一个Plan(计划树)。将Plan的根节点输入Executor执行语句。

Parser

parser.h为Parser类。输入query语句,返回解析的结果。private接口包含多个check,检验元信息实现语义分析。

parser.cpp的parseStatement函数是main直接调用的函数,第一步生成一个sqlparser类(调用第三方开源库接口生成的语法树)。然后在语法树的基础上校验元信息,对于不同的stmt使用不同的函数进行校验。

checkselectstmt,检验select。查看表是否存在,查看是否存在groupby、setOperation等操作,查看属性是否存在,查看where是否能实现,查看是否支持排序。

checkinsertstmt检验insert。校验表是否存在,属性是否存在,检验输入类型是否满足条件等。

matadata.h元信息管理。Table是单个表的信息,metadata创建哈希表存放表名到表的映射,管理所有表。

Optimizer

optimizer.h中定义了多种类型的算子(plantype)。在优化器中传入算子类型,优化器根据类型,通过switch,case语句创建对应算子。

createSelectPlanTree是创建选择算子的函数。首先从元信息的hashmap中获得表,然后获取表中的对应属性。然后创建一个ScanPlan(扫描算子),用于扫描数据。如果有where语句,生成过滤算子(filterplan),最后生成一个选择算子(用于最后结果的重新排序)。生成的算子只记录了基本的信息,并不会进行实际操作。

Executor

executor.h文件中TupleIter结构用于存放每一行数据。BaseOperator是操作的基类,对外接口只有执行函数。Executor类包含两部分,init将plan转化为operator,exec执行operator。

代码中每一次正确执行都会返回false。应为调用下层执行写在if从句中,所以正确执行不会执行if从句中的内容,而是继续执行后面判断指针是否为空的操作。如果下层返回true,则此层也返回true。

seqscanoperator的exec函数直接访问表。

存储引擎

storage.h中用双向链表连接每条数据。数据是变长的,需要通过表的元信息进行计算。TupleList为链表结构。TableStore是存储管理数据的部分,能插入、删除、更新数据。提供事务功能。parseTuple接口可以把data数组按照属性进行解析。

storage.cpp中TableStore类的构造函数首先会计算每一列需要多少空间。之后添加null标志位以及双向链表的两个指针的空间。

插入数据每次查看是否还有剩余空间,如果没有则申请100个并初始化。将新分配的内存放入freelist_中用于以后的存储。

事务实现

trx.h中定义了三种操作(插入、删除、更新)的undo日志。undo结构体定义了undo类型、对应的table、新老数据的位置。update的日志需要先copy一份老数据,然后将老数据更新。

3.使用

  1. 进入bin目录,执行./bydb进入数据库。
  2. create table db.t(a int, b varchar(20);创建表t。
  3. show tables;显示已有表。
  4. create table db.t2(a int, b varchar(10));创建另一个表t2。
  5. show columns db.t插卡表t属性。
  6. insert into db.t values(1, 'apple');向表t插入数据。此处必须为单引号。
  7. select * from db.t查看数据。
  8. update db.t set b = 'banana' where a = 1;更新表。
  9. begin;开始事务。可以用rollback;回滚或用commit提交。