自己动手写SQL执行引擎
自己动手写SQL执行引擎
前言
在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数据库底层原理的掌握是否牢靠。在笔者的github中给这个database起名为Freedom。
整体结构
既然造轮子,那当然得从前端的网络协议交互到后端的文件存储全部给撸一遍。下面是Freedom实现的整体结构,里面包含了实现的大致模块:
最终存储结构当然是使用经典的B+树结构。当然在B+树和文件系统block块之间的转换则通过Buffer(Page) Manager来进行。当然了,为了完成事务,还必须要用WAL协议,其通过Log Manager来操作。
Freedom采用的是索引组织表,通过DruidSQL Parse来将sql翻译为对应的索引操作符进而进行对应的语义操作。
MySQL Protocol结构
client/server之间的交互采用的是MySQL协议,这样很容易就可以和mysql client以及jdbc进行交互了。
query packet
mysql通过3byte的定长包头去进行分包,进而解决tcp流的读取问题。再通过一个sequenceId来再应用层判断packet是否连续。
result set packet
mysql协议部分最复杂的内容是其对于result set的读取,在NIO的方式下加重了复杂性。
Freedom通过设置一系列的读取状态可以比较好的在Netty框架下解决这一问题。
row packet
还有一个较简单的是对row格式进行读取,如上图所示,只需要按部就班的解析即可。
由于协议解析部分较为简单,在这里就不再赘述。
SQL Parse
Freedom采用成熟好用的Druid SQL Parse作为解析器。事实上,解析sql就是将用文本表示
的sql语义表示为一系列操作符(这里限于篇幅原因,仅仅给出select中where过滤的原理)。
对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 b.id and b.id>=19 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
码云链接
https://gitee.com/alchemystar/Freedom
自己动手写SQL执行引擎的更多相关文章
- 用scala实现一个sql执行引擎-(上)
前言 在实时计算中,通常是从队列中收集原始数据,这种原始数据在内存中通常是一个java bean,把数据收集过来以后,通常会把数据落地到数据库,供后面的ETL使用.举个一个简单的例子,对一个游戏来说, ...
- 用scala实现一个sql执行引擎-(下)
执行 上一篇讲述了如何通过scala提供的内置DSL支持,实现一个可以解析sql的解析器,这篇讲如何拿到了解析结果-AST以后,如何在数据上进行操作,得到我们想要的结果.之前说到,为什么选择scala ...
- spark sql 执行计划生成案例
前言 一个SQL从词法解析.语法解析.逻辑执行计划.物理执行计划最终转换为可以执行的RDD,中间经历了很多的步骤和流程.其中词法分析和语法分析均有ANTLR4完成,可以进一步学习ANTLR4的相关知识 ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
- SQL Server中的执行引擎入门
简介 当查询优化器(Query Optimizer)将T-SQL语句解析后并从执行计划中选择最低消耗的执行计划后,具体的执行就会交由执行引擎(Execution Engine)来进行执行.本文旨在 ...
- MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询
对于一对一,一对多的关联查询,Mybatis-Plus官方示例(mybatis-plus-sample-resultmap)在处理时,需要编写查询方法及配置resultMap,并且写SQL. 为了简化 ...
- 曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎
曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎 写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean De ...
- 规则引擎集成接口(四)SQL执行语句
SQL执行语句 右键点击数据库连接文件“hr”—“添加SQL执行语句”,如下图: 弹出窗体,如下图: 将显示名称改为“部门名称”,返回至类型设置为“string”,在编写sql语句,如下图: 点击确定 ...
- 【原创】自己动手写工具----XSmartNote [Beta 3.0]
一.前面的话 在动笔之前,一直很纠结到底要不要继续完成这个工具,因为上次给它码代码还是一年多之前的事情,参考自己动手写工具----XSmartNote [Beta 2.0],这篇博文里,很多园友提出了 ...
随机推荐
- uni-app高分开源电影项目源码案例分析,支持一套代码发布小程序、APP平台多个平台(前端入门必看)
uni-app-Video 一个优秀的uni-app案例,旨在帮助大家更快的上手uni-app,共同进步! Features 代码编写简洁,注释清晰,快速入门必备: 支持在线模糊搜索: 程序类目懒 ...
- Mysql 常用函数(19)- mod 函数
Mysql常用函数的汇总,可看下面系列文章 https://www.cnblogs.com/poloyy/category/1765164.html mod 的作用 求余数,和%一样 mod的语法格式 ...
- CSS之未知高度img垂直居中
测试代码如下:(能够水平居中,通过text-align:center实现) <style>.box{ width:800px;height:600px;border:2px solid # ...
- masonry中的make,remake,update
- (void)viewDidLoad { [super viewDidLoad]; self.navigationController.navigationBar.translucent = NO; ...
- Java并发包2--ThreadLocal的使用及原理浅析
ThreadLocal 是本地线程变量,是一个以ThreadLocal对象为key,任意对象为value的存储结构. 一.使用案例 1.定义线程类MyThread,代码如下: public class ...
- Kattis - entertainmentbox
题目链接:https://vjudge.net/problem/Kattis-entertainmentbox 题目大意: 一种叫做不知道什么的盒子可以同时录 k 个节目,现给出 n 个节目的开始和结 ...
- ShoneSharp语言(S#)软件更新13.6版
ShoneSharp语言(S#)编辑解析运行器 软件更新13.6版 作者:Shone 今天把近期发现的各种软件问题做了修改,并发布新版ShoneSharp.13.6.exe,最新的网盘链接为: htt ...
- [开源] .Net orm FreeSql 1.5.0 最新版本(番号:好久不见)
废话开头 这篇文章是我有史以来编辑最长时间的,历时 4小时!!!原本我可以利用这 4小时编写一堆胶水代码,真心希望善良的您点个赞,谢谢了!! 很久很久没有写文章了,上一次还是在元旦发布 1.0 版本的 ...
- Unity实现byte[]合成图像
bool CreateCovers(byte[] imageData) { Texture2D imageTexture = new Texture2D(273, 126); imageTexture ...
- 来自AI的Tips——情景智能
来自AI的Tips--情景智能 上一次我们介绍了华为快服务智慧平台是什么,今天我们来侃一侃平台最有代表性的一个流量入口--情景智能(AI Tips). 首先情景智能在哪呢?大家可以拿出自己的华 ...