本身动手写SQL执行引擎

本身动手写SQL执行引擎

前言

在阅读了大量关于数据库的资料后,笔者不由自主产生了一个造数据库轮子的想法。来验证一下本身对于数据库底层原理的掌握是否牢靠。在笔者的github中给这个database起名为Freedom。前端

总体结构

既然造轮子,那固然得从前端的网络协议交互到后端的文件存储所有给撸一遍。下面是Freedom实现的总体结构,里面包含了实现的大体模块:

最终存储结构固然是使用经典的B+树结构。固然在B+树和文件系统block块之间的转换则经过Buffer(Page) Manager来进行。固然了,为了完成事务,还必需要用WAL协议,其经过Log Manager来操做。
Freedom采用的是索引组织表,经过DruidSQL Parse来将sql翻译为对应的索引操做符进而进行对应的语义操做。java

MySQL Protocol结构

client/server之间的交互采用的是MySQL协议,这样很容易就能够和mysql client以及jdbc进行交互了。node

query packet

mysql经过3byte的定长包头去进行分包,进而解决tcp流的读取问题。再经过一个sequenceId来再应用层判断packet是否连续。
mysql

result set packet

mysql协议部分最复杂的内容是其对于result set的读取,在NIO的方式下加剧了复杂性。
Freedom经过设置一系列的读取状态能够比较好的在Netty框架下解决这一问题。
git

row packet

还有一个较简单的是对row格式进行读取,如上图所示,只须要循序渐进的解析便可。

因为协议解析部分较为简单,在这里就再也不赘述。github

SQL Parse

Freedom采用成熟好用的Druid SQL Parse做为解析器。事实上,解析sql就是将用文本表示
的sql语义表示为一系列操做符(这里限于篇幅缘由,仅仅给出select中where过滤的原理)。sql

对where的处理

例如where后面的谓词就能够表示为一系列的以树状结构组织的SQL表达式,以下图所示:

当access层经过游标提供一系列row后,就能够经过这个树状表达式来过滤出符合where要求的数据。Druid采用了Parse中经常使用的visitor很方便的处理上面的表达式计算操做。数据库

对join的处理

对join最简单处理方案就是对两张表进行笛卡尔积,而后经过上面的where condition进行过滤,以下图所示:
后端

Freedom对于缩小笛卡尔积的处理

因为Freedom采用的是B+树做为底层存储结构,因此能够经过where谓词来界定B+树scan(搜索)的范围(也即最大搜索key和最小搜索key在B+树种中的位置)。考虑sql网络

select a.*,b.* from t_archer as a join t_rider as b where a.id>=3 and a.id<=11 and b.id>=19 and b.id<=31

那么就能够界定出在id这个索引上,a的scan范围为[3,11],以下图所示:

b的scan范围为[19,31],以下图所示(假设两张表数据同样,便于绘图):

scan少了从原来的15*15(一共15个元素)次循环减小到4*4次循环,即循环次数减小到7.1%

固然若是存在join condition的话,那么Freedom在底层cursor递归处理的过程当中会预先过滤掉一部分数据,进一步减小上层的过滤。

B+Tree的磁盘结构

leaf磁盘结构

Freedom的B+Tree是存储到磁盘里的。考虑到存储的限制以及不定长的key值,因此会变得很是复杂。Freedom以page为单位来和磁盘进行交互。叶子节点和非叶子节点都由page承载并刷入磁盘。结构以下所示:

一个元组(tuple/item)在一个page中分为定长的ItemPointer和不定长的Item两部分。
其中ItemPointer里面存储了对应item的起始偏移和长度。同时ItemPointer和Item如图所示是向着中心方向进行伸张,这种结构颇有效的组织了非定长Item。

leaf和node节点在Page中的不一样

虽然leaf和node在page中组织结构一致,但其item包含的项确有区别。因为Freedom采用的是索引组织表,因此对于leaf在聚簇索引(clusterIndex)和二级索引(secondaryIndex)中对item的表示也有区别,以下图所示:

其中在二级索引搜索时经过secondaryIndex经过index-key找到对应的clusterId,再经过
clusterId在clusterIndex中找到对应的row记录。
因为要落盘,因此Freedom在node节点中的item里面写入了index-key对应的pageno,
这样就能够容易的从磁盘恢复全部的索引结构了。

B+Tree在文件中的组织

有了Page结构,咱们就能够将数据承载在一个个page大小的内存里面,同时还能够将page刷新到对应的文件里。有了node.item中的pageno,咱们就能够较容易的进行文件和内存结构之间的互相映射了。
B+树在磁盘文件中的组织以下图所示:

B+树在内存中相对应的映射结构以下图所示:

文件page和内存page中的内容基本是一致的,除了一些内存page中特有的字段,例如dirty等。

每一个索引一个B+树

在Freedom中,每一个索引都是一颗B+树,对记录的插入和修改都要对全部的B+树进行操做。

B+Tree的测试

笔者经过一系列测试case,例如随机变长记录对B+树进行插入并落盘,修复了其中若干个很是诡异的corner case。

B+Tree的todo

笔者这里只是完成了最简单的B+树结构,没有给其添加并发修改的锁机制,也没有在B+树作操做的时候记录log来保证B+树在宕机等灾难性状况下的一致性,因此就算完成了这么多的工做量,距离一个高并发高可用的bptree还有很是大的距离。

Meta Data

table的元信息由create table所建立。建立以后会将元信息落盘,以便Freedom在重启的时候加载表信息。每张表的元信息只占用一页的空间,依旧复用page结构,主要保存的是聚簇索引和二级索引的信息。元信息对应的Item以下图所示:

若是想让mybatis能够自动生成关于Freedom的代码,还需实现一些特定的sql来展示Freedom的元信息。这个在笔者另外一个项目rider中有这样的实现。原理以下图所示:

实现了上述4类SQL以后,mybatis-generator就能够经过jdbc从Freedom获取元信息进而自动生成代码了。

事务支持

因为当前Freedom并无保证并发,因此对于事务的支持只作了最简单的WAL协议。经过记录redo/undolog从而实现原子性。

redo/undo log协议格式

Freedom在每作一个修改操做时,都会生成一条日志,其中记录了修改前(undo)和修改后(redo)的行信息,undo用来回滚,redo用来宕机recover。结构以下图所示:

WAL协议

WAL协议很好理解,就是在事务commit前将当前事务中所产生的的全部log记录刷入磁盘。
Freedom天然也作了这个操做,使得能够在宕机后经过log恢复出全部的数据。

回滚的实现

因为日志中记录了undo,因此对于一个事务的回滚直接经过日志进行undo便可。以下图所示:

宕机恢复

Freedom若是在page所有刷盘以后关机,则能够由经过加载page的方式获取原来的数据。
但若是忽然宕机,例如kill -9以后,则能够经过WAL协议中记录的redo/undo log来从新
恢复全部的数据。因为时间和精力所限,笔者并无实现基于LSN的检查点机制。

Freedom运行

git clone https://github.com/alchemystar/Freedom.git
// 并无作打包部署的工做,因此最简单的方法是在java编辑器里面
run alchemystar.freedom.engine.server.main

如下是笔者实际运行Freedom的例子:

join查询

delete回滚

Freedom todo

Freedom还有不少工做没有完成,例若有层次的锁机制和MVCC等,因为工做忙起来就耽搁了。
因而笔者就看了看MySQL源码的实现理解了一下锁和MVCC实现原理,并写了两篇博客。比起
本身动手撸实在是轻松太多了_

MVCC

https://my.oschina.net/alchemystar/blog/1927425

二阶段锁

https://my.oschina.net/alchemystar/blog/1438839

尾声

在造轮子的过程当中一开始是很是有激情很是快乐的。但随着系统愈来愈庞大,复杂性愈来愈高,进度就会愈来愈慢,还时不时要推翻本身原来的设想并从新设计,而后再协同修改关联的全部代码,就如同泥沼,越陷越深。至此,笔者才领悟了软件工程最重要的实际上是控制复杂度!始终保持简洁的接口和优雅的设计是实现一个大型系统的必要条件。

收获与遗憾

此次造轮子的过程基本知足了笔者的初衷,经过写一个数据库来学习数据库。不只仅是加深了理解,最重要的是笔者在写的过程当中终于明白了数据库为何要这么设计,为何不那样设计,仅仅对书本的阅读可能并不会有这些思考与领悟。
固然,仍是有不少遗憾的,Freedom并无实现锁机制和MVCC。因为只能在工做闲暇时间写,因此断断续续写了一两个月,工做一忙就将这个项目闲置了。如今将Freedom的设计写出来,但愿你们能有所收获。

github连接

https://github.com/alchemystar/Freedom