Sqlite是如何工作的?

2020-06-28 02:30:22

今天晚上,神奇的卡马兰让我坐下来,比我们以前学到了更多关于数据库的知识。

我想破解SQLite,因为我以前用过它,它不需要配置,也不需要单独的服务器进程,我被告知它的源代码写得很好,很容易接近,所有的数据都存储在一个文件中。太棒了!

只有一个主键和一个字符串!还有什么比这更简单的呢?然后,我编写了一些Python脚本,将/usr/share/dict/words的内容放入数据库:

将sqlite3c=sqlite3.connect(";./fun.sqlite";)with open(';/usr/share/dict/words';)导入为f:对于i,枚举中的Word(F):Word=word.strid()word=Unicode(Word,';latin1';)c.Execute(";插入到有趣的值(?,?);";,(i,Word))。

太棒了!现在,我们有一个名为fun.sqlite foreexperientation的4MB数据库。对于二进制文件,我最喜欢做的第一件事就是对它们进行分类。这非常有效,但是Kamal指出,当然,十六进制转储是查看二进制文件的更好方式。HEXDUMP-C fun.sqlite的输出如下所示:

我在本文中粘贴了六进制转储的前几千行,这样您可以更仔细地查看。您将看到该文件交替拆分为单词和胡言乱语-将会有一个主要是单词的序列,然后是不可读的无稽之谈。

这个原因当然有押韵之处!SQLite数据库的出色编写的文件格式告诉我们,SQLite数据库被分成多个页面,文件的第16和17字节就是页面大小。

00000000 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00|SQLite格式3.|00000010 04 00 01 01 00 40 20 20 00 00 00 27 00 000c be|[电子邮件受保护].';.|^页面大小:)

所以我们的页面大小是0x0400字节,或1024字节,或1k。因此,这个数据库被分割成一堆1k的块,称为页面。

我们的FUN表的id列上有一个索引,它允许我们快速运行像SELECT*FROM FUN WHERE id=100这样的查询。更准确地说:要查找第100行,我们不需要阅读每一页,只需阅读几页即可。我总是以模糊的方式理解索引-我知道它们是“某种树”,它允许您访问O(Logn)中的数据,尤其是数据库使用的是一种称为btree的东西。我仍然不知道btree是什么。让我们看看还能不能做得更好!

这就是它开始变得真正有趣的地方!我下载了sqlitessource代码,Kamal和我想出了如何编译它(使用nix,这完全是另一回事)。

然后我放入一条打印语句,这样它每次访问页面时都会告诉我。大约有14万行SQLite源代码,这有点吓人!

/*//*。*//*2009年1月28日*作者放弃此源代码的版权。代替**法律通知,这里有一个祝福:*愿你行善不作恶。**愿你宽恕自己,宽恕他人。**愿你自由分享,从不超过您的备份此文件包含sqlite3backupxxx()**give.**函数和相关特性的实现。

我的下一个目标是让SQLite告诉我它是如何遍历页面的。对140,000行进行了一些仔细的整理后,我们找到了这个函数btreePageFromDbPage。所有页面读取都需要通过此函数,因此我们只需向其添加一些日志:)。

/*将从寻呼机获取的DbPage转换为**btree层使用的MemPage。*/static MemPage*btreePageFromDbPage(DbPage*pDbPage,Pgno pgno,BtShared*pbt){MemPage*ppage=(MemPage*)sqlite3PagerGetExtra(PDbPage);ppage->;adata=sqlite3Page3Page3Page。页页->;hdrOffset=页页->;pgno==1?100:0;返回页页;}。

现在它每读一页就会通知我们!干净利落!我们来做个小实验。

sqlite&>SELECT*FROM FUN WHERE id=1;读取btree页,页号1读取btree页,页号5读取btree页,页号828读取btree页,页号10读取btree页,页号2读取btree页,页号76读取btree页,页号61|A';ssqlite&>;SELECT*FROM FUN where id=20;读取btree页,页号1读取。

这两行(第1行和第20行)在同一页中,因此它遍历相同的路径才能到达这两行!

SQLITE&>SELECT*FROM FUN WHERE id=200;读取btree页,页号1读取btree页,页号5读取btree页,页号828读取btree页,页号11读取btree页,页号2读取btree页,页号76阅读btree页,页号2818200|aggie。

显然,200在树上是相当接近的,但它需要在最后转到2818。而80000要远得多:

SQLITE&>SELECT*FROM FUN WHERE id=80000;读取btree页,页号1读取btree页,页号5阅读btree页,页号1198阅读btree页,页号992阅读btree页,页号2阅读btree页,页号1813阅读btree页,页号44980000|scarfs。

如果我们返回并检查该文件,我们可以看到页面1、5、1198、992、2和1813是内部节点-其中没有数据,只有指向其他页面的指针。第6、2818和449页是叶节点,它们是数据所在的位置。

我仍然不太清楚内部页面是如何构建的,以及指向其子页面的指针是如何工作的。现在是睡觉的时间了,但也许那会在以后的某一天发生!

修改开放源码程序以打印出调试信息,以便更好地了解它们的内部结构:非常有趣。