返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >如何自己动手写SQL执行引擎
  • 893
分享到

如何自己动手写SQL执行引擎

2024-04-02 19:04:59 893人浏览 泡泡鱼
摘要

目录前言整体结构Mysql Protocol结构B+Tree的磁盘结构事务支持尾声前言 在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数

前言

在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数据库底层原理的掌握是否牢靠。在笔者的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 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回滚

尾声

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

github链接:Https://github.com/alchemystar/Freedom

以上就是如何自己动手写SQL执行引擎的详细内容,更多关于自己动手写SQL执行引擎的资料请关注编程网其它相关文章!

--结束END--

本文标题: 如何自己动手写SQL执行引擎

本文链接: https://lsjlt.com/news/127325.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何自己动手写SQL执行引擎
    目录前言整体结构MySQL Protocol结构B+Tree的磁盘结构事务支持尾声前言 在阅读了大量关于数据库的资料后,笔者情不自禁产生了一个造数据库轮子的想法。来验证一下自己对于数...
    99+
    2024-04-02
  • V8引擎如何执行JavaScript代码
    V8引擎如何执行JavaScript代码,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。题目中说到的V8引擎,大家自然会联想到Node.js。我们先看一下官方对Node.js的定...
    99+
    2023-06-16
  • python如何引用自己写的模块
    要引用自己写的模块,可以按照以下步骤进行操作:1. 在你的模块所在的文件夹中创建一个空白的`__init__.py`文件。这个文件是...
    99+
    2023-09-25
    python
  • 如何在自己的网站安装一个搜索引擎
    这篇文章将为大家详细讲解有关如何在自己的网站安装一个搜索引擎,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。  1、安装自己的搜索引擎脚本  安装Perl搜索引擎脚本或PHP搜索引擎的脚本 。 这需要你在设...
    99+
    2023-06-10
  • vue如何自动执行函数
    在vue中自动执行函数的方法:1.新建vue.js项目;2.在项目中创建组件;3.在组件中定义函数;4.使用created方法自动执行函数;具体步骤如下:首先,在vue-cli中创建一个vue.js项目;vue create project...
    99+
    2024-04-02
  • dede如何按自己写的ID进行排序
    更改一下函数,实现排序方式根据自己写的ID排序就好了。 方法: 1、打开include/taglib/channelartlist.lib.php,找到大约78行,把 复制代码代码如下: $dsql->SetQue...
    99+
    2022-06-12
    dede排序 ID排序
  • 如何自动生成批量执行SQL脚本的批处理
    这篇文章主要介绍如何自动生成批量执行SQL脚本的批处理,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!场景: DBA那边给我导出了所有的存储、函数等等对象的创建脚本,有上千个文件. 现在需要将这些对象创建脚本导入到另外...
    99+
    2023-06-08
  • java如何运行自己编写的程序文件
    要运行自己编写的Java程序文件,可以按照以下步骤进行:1. 编写Java程序文件:使用任何文本编辑器,创建一个具有".java"扩...
    99+
    2023-09-28
    java
  • php如何实现代码自动执行
    本文小编为大家详细介绍“php如何实现代码自动执行”,内容详细,步骤清晰,细节处理妥当,希望这篇“php如何实现代码自动执行”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、PHP的自动执行功能PHP提供了多种方...
    99+
    2023-07-06
  • 如何在SQLServer中执行动态SQL语句
    在SQL Server 中执行动态 SQL 语句通常可以通过以下几种方式实现: 使用 sp_executesql 存储过程:sp_...
    99+
    2024-04-09
    SQLServer
  • 如何使用pyinstaller打包时引入自己编写的库
    目录pyinstaller打包引入自己编写的库场景解决方案使用第三方库pyinstallerpyinstaller打包引入自己编写的库 场景 使用pyinstaller打包某个文件后...
    99+
    2024-04-02
  • 如何在MySQL中使用C#编写自定义存储引擎
    如何在MySQL中使用C#编写自定义存储引擎摘要:MySQL是一个流行的关系型数据库管理系统,提供了许多内置的存储引擎,诸如InnoDB、MyISAM等。然而,有时候我们需要自定义存储引擎来满足特定的需求。本文将介绍如何使用C#编写自定义存...
    99+
    2023-10-22
    MySQL C# 存储引擎
  • 如何在MySQL中使用JavaScript编写自定义存储引擎
    如何在MySQL中使用JavaScript编写自定义存储引擎介绍随着数据量和业务需求的增加,传统的关系型数据库已经无法满足全部的需求。此时,我们可以通过自定义存储引擎,根据特定的需求优化数据库的性能和功能。而MySQL提供了自定义存储引擎的...
    99+
    2023-10-22
    MySQL JavaScript 自定义存储引擎
  • 如何实现CentOS开机自动运行自己的脚本
    这篇文章将为大家详细讲解有关如何实现CentOS开机自动运行自己的脚本,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。操作系统:CentOS-5.2目标:开机自动挂载局域网中windows的共享目录一、ro...
    99+
    2023-06-10
  • oracle 定时任务 如何手动立即执行
    通过使用 dbms_job.run 过程,可以立即执行 oracle 定时任务,无需等待其计划的时间运行。步骤包括:查找任务的作业名称。使用该作业名称运行 dbms_job.run 命令...
    99+
    2024-04-19
    oracle
  • 如何进行Spark SQL在100TB上的自适应执行实践
    本篇文章给大家分享的是有关如何进行Spark SQL在100TB上的自适应执行实践,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Spark SQL是Apache Spark最广...
    99+
    2023-06-02
  • php如何在虚拟主机上自动执行
    要在虚拟主机上自动执行PHP代码,可以按照以下步骤进行操作:1. 创建一个以 .php 结尾的 PHP 文件,例如 index.ph...
    99+
    2023-08-25
    虚拟主机 php
  • SpringBoot如何启动并初始化执行sql脚本
    这篇“SpringBoot如何启动并初始化执行sql脚本”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“SpringBoot如...
    99+
    2023-07-05
  • Python如何手动编写一个自己的LRU缓存装饰器的方法实现
    LRU缓存算法,指的是近期最少使用算法,大体逻辑就是淘汰最长时间没有用的那个缓存,这里我们使用有序字典,来实现自己的LRU缓存算法,并将其包装成一个装饰器。 1、首先创建一个my_c...
    99+
    2024-04-02
  • 如何手动实现一个 JavaScript 模块执行器
    今天就跟大家聊聊有关如何手动实现一个 JavaScript 模块执行器,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。如果给你下面这样一个代码片段(动...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作