独自handle一个数据库大程有感

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

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轮子叔曾经说过的话作结尾吧。ui

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

相关文章
相关标签/搜索