这学期数据库课程,最后的大程是写一个MiniSQL的数据库实现,要求很简单,建删表,建删单值索引,支持主键和unique定义,支持最简单的select,只要支持3个类型:int,float,char(0~255)。最开始,考虑到数据库的运行时确定类型的特点,选择了运行时强大的C#,还能顺便集成进Linq。但是一周后发现C#操作对象二进制结构的能力几乎为0,在写BufferManager的时候也发现完全不能自由的控制对象生命周期,并且IDisposable的实现也过于迷,与强大运行时和linq相比,这些弱项是我无法接受的,随即果断转到C++。

C++的RAII提供了精确的对象生命周期和所有权控制,这使我很愉快地写出了能精确控制每一个内存块的BufferManager,智能指针,尤其是unique_ptr在其中起到了巨大的作用,还顺便了解了C++11更加精细的对象左右值的概念,更加熟悉了对象所有权和所有权转移的界定和控制。C++足够接近底层的指针和内存操作,也使得我能直接将一个对象指针reinterpret_cast成byte*,随后直接用二进制模式写回文件(当然这里面我手动控制了对象不能有指针和堆内存),也可以从文件中读入一段二进制内容,直接将首地址reinterpret_cast成对象指针,然后直接使用。模板在其中也帮了很大的忙。

索引的部分,最开始使用了模板B+树,随后发现数据库需要运行时类型,模板这种编译时静态类型的东西不能满足我的需求,于是让模板继承自公用的基类,擦除模板类型,基类的虚函数参数使用byte*,擦除参数类型,通过虚函数调用找到正确的派生类后再由派生类恢复参数的类型参与运算,这样算是一种应急的walk around,毕竟当时的时间不允许我把模板容器改成运行时动态类型容器,也没有时间再造一个动态类型轮子。在把运行时的值转为静态的模板类型时,费了很大的劲,主要是需要实现类似于

template<typename T>
TreeBase* make_tree(); switch(type)
{
case Int:
return make_tree<int>();
case Float:
return make_tree<float>();
case Char:
switch(size)
{
case 1:
return make_tree<array<char, 1>>();
case 2:
return make_tree<array<char, 2>>();
//.....
case 255:
return make_tree<array<char, 255>>();
}
break;
}

类似这样的把动态的值转为静态类型的东西,最恶心的部分是那个switch(size)的部分,要case 1~255,手写的话肯定恶心的要死,于是我写了个模板来做二分搜索,最多6层函数调用就能找到正确的值。糊了这么多,还是为了填当初写了个静态的模板B+树的坑。B+树debug的过程中,使用了VS Debugger提供的natvis可视化工具,来自定义我自己写的类在监视窗口中的显示方式,这东西确实爽,看东西方便了不少。

模板B+树的实现内部坑更多,当初的设计是既然所有类型都是静态的,那就可以静态的确定一个树的节点的度,并且可以使得节点大小正好撑满4KB空间,实现起来出了坑,因为迷之内存对齐占据了空间,使用(4KB – sizeof(非指针成员))/sizeof(指针大小)的方式确定的树节点,总是会使得大小超过4KB,幸亏提前猜到了可能出现这样的问题,加了static_assert,不然又不知道要debug到哪里去,话说回来,最后用了#pragma pack(1)这种坑爹货,关了内存对齐,勉强绕过了这个问题。下面的坑就是来自树节点本身,由于我要把整个节点直接二进制写入文件,所以至少需要保证节点是POD类型(虽然后面发现这样做其实多此一举,还增加复杂度),这也就导致了节点内不能放任何成员函数,于是我又写了一个包装类,持有一个树节点,然后在这个包装类里写需要的成员函数,由于树操作需要同时读多个节点,每个节点是一个block,为了防止读后面节点时前面要用的节点被回收的情况出现,我设计了在包装类在包装节点时自动给节点加锁的机制,这有些类似于shared_ptr的引用计数,随后包装类的锁计数控制又出了坑,这让我明白原来不写的move ctor编译器是会自动生成,而不是把move操作变为copy,因此这里有点怀疑老版C++代码中如果做了类似引用计数的机制,是否能直接在C++11环境下正常运行。

解释器的部分手写了tokenizer,用了vczh轮子叔在他博客里给tinymoe写tokenizer时手写状态机的方法,语法分析是单纯的递归下降同时解释执行。

剩下的部分就不具备太多的技术含量,只是单纯的业务逻辑,唯一的坑点就是我在这个数据库里写了三份管理字符串到数字映射的东西,竟然没想到用hash来做,失败,失败。

最后的测试和运行阶段,跑了一遍VS自带的性能分析工具,真的找到了限制性能的地方,是在lru算法替换文件块的部分,被替换块的选择出了坑,导致块交换频率过高,拉低了程序性能,优化之后,插入1W条数据的同时检查3个unique键并插入索引,只需要20秒多一点,还是比较满意的。

写这个东西的过程中还是发现了C++标准库有很多缺少的东西的,比如序列化(这个在未来有了static reflection应该会有所改善,当然现在也有很多序列化开源库),比如花式拼接字符串(sstream太慢),比如二进制文件流(boost有个buffer_stream,但是太老),比如运行时确定规模并且存储空间连续的二(多)维数组(new是不能new int[m][n]的,n必须是字面值),还有char*和string转换时的麻烦等。

一个人handle这样一个最终写了5000多行的东西,中间用到了各种自己学到的C++技术,尝试了各种从前没用过的工具比如natvis,profiler,还在debug B+树的时候刷了夜,解锁了一大堆成就,虽然最后数据库的分数不是很高(我猜多半是我期中期末考得太烂了,大程目测分不低),还是感觉很有收获的,说了这么多,最后以一句vczh轮子叔曾经说过的话做结尾吧。

你需要花时间做什么,取决于这个问题是不是够难,是不是刚刚好你可以做出来,再难一点点你就做不出来了。只要你保持这种训练方法长达十年,想不牛逼都难。

独自handle一个数据库大程有感的更多相关文章

  1. mssqlserver数据导出到另外一个数据库

    mssqlserver数据导出到另外一个数据库 准备源数据库,找到想要导出的数据库,右键选择"任务"再选择"导出数据" 设置源数据库信息 3.设置目标数据库,导 ...

  2. 数据库大作业--由python+flask

    这个是项目一来是数据库大作业,另一方面也算是再对falsk和python熟悉下,好久不用会忘很快. 界面相比上一个项目好看很多,不过因为时间紧加上只有我一个人写,所以有很多地方逻辑写的比较繁琐,如果是 ...

  3. 通过 SQL Server 视图访问另一个数据库服务器表的方法

    今天项目经理跑过来对我大吼大叫说什么之前安排让我做一大堆接口为什么没做,我直接火了,之前明明没有这个事情…… 不过事情还要解决,好在两个项目都是用的sqlserver,可以通过跨数据库视图来快速解决问 ...

  4. 怎么用sql语句查询一个数据库有多少张表

    今天在技术群中闲谈时忽然聊到一个问题,那就是当一个数据库中有多张表时怎么快速的获取到表的个数,从而给问询者一个准确的回答. 大家或许会说,这个问题和我们的数据库操作没有太大关系或者不是很挂钩,所以没意 ...

  5. Druid:一个用于大数据实时处理的开源分布式系统

    Druid是一个用于大数据实时查询和分析的高容错.高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分析.尤其是当发生代码部署.机器故障以及其他产品系统遇到宕机等情况时,Druid仍 ...

  6. 有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果(转)

    引用 前几天在网上看到一个淘宝的面试题:有一个很大的整数list,需要求这个list中所有整数的和,写一个可以充分利用多核CPU的代码,来计算结果.一:分析题目 从题中可以看到“很大的List”以及“ ...

  7. 如何用 php 读取一个很大的 excel 文件。

    这个程序是用php 读取一个很大的excel文件, 先将 excel 文件保存成csv 文件, 然后利用 迭代器 逐行读取 excel 单元格的值, 拿到值以后 做相应处理,并打印结果. <?p ...

  8. Druid:一个用于大数据实时处理的开源分布式系统——大数据实时查询和分析的高容错、高性能开源分布式系统

    转自:http://www.36dsj.com/archives/28590 Druid 是一个用于大数据实时查询和分析的高容错.高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分 ...

  9. 大项目之网上书城(八)——数据库大改&添加图书

    目录 大项目之网上书城(八)--数据库大改&添加图书 主要改动 1.数据库新增表 代码 2.数据库新增触发器 3.其他对BookService和BookDao的修改 代码 4.addBook. ...

随机推荐

  1. 【Mocha.js 101】钩子函数

    前情提要 在上一篇文章<[Mocha.js 101]同步.异步与 Promise>中,我们学会了如何对同步方法.异步回调方法以及 Promise 进行测试. 在本篇文章中,我们将了解到 M ...

  2. ubuntu eclipse 中安装 python + PyDev

    参照网络和个人总结 系统配置:ubuntu12.04       jdk:1.6      eclipse:3.4 首先你的系统必须安装好pyton .也ubuntu系统自带的 刚开始以为是jdk安装 ...

  3. rand & random & arc4random

    rand(3) / random(3) / arc4random(3) / et al. Written by Mattt Thompson on August 12th, 2013 What pas ...

  4. ionic 开发APP 安装配置详解以及 cordova 环境配置详细过程

    整个安装过程:     1. jdk 1.7.2   (http://www.oracle.com/technetwork/java/javase/downloads/index.html) 安装好之 ...

  5. rtmp转m3u8

    不是所有的地址改成这样都能播 需要自己测试 先说一下rtmp的其中rtmp的常见的差不多是3种 1.一种是wowza服务器的 比如这个地址rtmp://116.55.245.135:8096/live ...

  6. js中的break ,continue, return (转)

    面向对象编程语法中我们会碰到break ,continue, return这三个常用的关键字,那么关于这三个关键字的使用具体的操作是什么呢?我们在使用这三关键字的时候需要注意和需要理解的规则是什么呢? ...

  7. 当padding,margin,top为百分比值,具体数值如何计算

    padding以及margin的四个方向设置值为百分数的时候,是以父元素的宽度为基数来进行计算. (为了保持横向和纵向方向上的margin/padding一致,但是其实也不是一定以父元素的宽度为参照物 ...

  8. Surprise团队第四周项目总结

    Surprise团队第四周项目总结 项目进展 这周我们小组的项目在上周的基础上进行了补充,主要注重在注册登录界面的改进优化与美观,以及关于人计算法的学习与初步实现. 我们小组针对上次APP中界面出现的 ...

  9. netezza 数据库 取 季初 季末 时间

    -- 取季初那一天 select date_trunc( 'quarter',cast('20150820' as date)) -- 取季末那一天 select add_months(date_tr ...

  10. vm虚拟机安装雨林木风ghost镜像

    每次安装总是提示没办法加载镜像,或者镜像不存在,总之就是读取不到光驱里的镜像文件. 这是需要注意的两点:cd光驱模式设置为IDE,不能是scsi和sata两种模式,然后再进入winpe系统就行.