SQLite数据库的工作原理分析
这篇文章主要为大家展示了"SQLite数据库的工作原理分析",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"SQLite数据库的工作原理分析"这篇文章吧。
SQLite 是一个小型数据库应用程序,用于数百万个软件和设备。SQLite 是由 D.Richard Hipp 于 2000 年 8 月发明的。SQLite 是一种高性能、轻量级的关系数据库。如果您愿意在编码级别学习数据库的内部结构,那么 SQLite 是最好的开源数据库,它具有高度可读的源代码和大量文档。阅读更高版本的 SQLite 变得有点困难,因为它包含许多新功能。为了理解数据库内部的基本实现,你应该对数据结构有很好的了解,一些关于计算理论的知识,以及操作系统是如何工作的。
在这里,我们将研究 SQLite 2.5.0 版本。您可以在GitHub上找到SQLite 后端的简单实现。 另外,我发现这个存储库 包含用于代码读取的 SQLite 2.5.0 实现。
什么是数据库?
将数据保存在平面文件中读取和存储数据的效率不高。数据库以适当的顺序组织数据,以便数据读取和写入速度更快。数据可以是结构化的、半结构化的或非结构化的。数据库主要用于存储结构化和半结构化数据。可以根据要存储的数据结构类型对数据库进行如下挖掘。
关系型数据库:常用的具有表结构的数据库类型。表可以与其他表有关系。SQL 语言用于操作此类数据库上的数据。
键值数据库:与关联的键一起存储的数据。可以使用给定的键检索数据。内存数据库通常是这种类型的数据库。
对象数据库:数据结构更像是一个对象而不是一个表。
图数据库:图数据库是节点和边的集合,主要用于数据挖掘和社交媒体应用程序。
SQLite 数据库架构
SQLite 数据库架构分为两个不同的部分,分别命名为核心和后端。核心部分包含接口、分词器、解析器、代码生成器和虚拟机,它们为数据库事务创建执行顺序。后端包含 B-tree、Pager 和 OS 接口来访问文件系统。Tokenizer、Parser 和代码生成器统称为编译器,它生成一组运行在虚拟机上的操作码。
从哪里开始?
要了解数据库的架构,您需要具备以下先决条件。
对数据结构和算法有很好的理解。尤其是 B 树、链表、Hashmaps 等数据结构。
对计算机体系结构有一定的了解。如何读写磁盘,分页和缓存如何工作。
有限自动机、上下文无关语法等理论计算机和一些正则表达式知识。
SQLite 架构
VFS(虚拟文件系统)
Unix 和 Windows 上的文件访问彼此不同。VFS 提供通用 API 来访问文件,而无需考虑其运行的操作系统类型。该 API 包括打开、读取、写入和关闭文件的函数。以下是 VFS 中用于读取、写入数据到文件中的一些 API。
/* Create a connection to file to read write zFilename : file name id : file pointer pReadonly : read or write */int sqliteOsOpenReadWrite(const char *zFilename, OsFile *id,int *pReadonly);/* Acqure the lock to read file. This function should be called before caling to any file read function. Return 0 if successid : file pointer */int sqliteOsReadLock(OsFile *id);/* Get the write lock to write into a file. This function should called beforedoing any write operation into the file system.Return 0 if successid : file pointer*/int sqliteOsWriteLock(OsFile *id);/* Move to the given number of offest to read or write into the file*/int sqliteOsSeek(OsFile *id, int offset);/* Read amt bytes from the file with offset pointed by sqliteOsSeek*/int sqliteOsRead(OsFile *id, void *pBuf, int amt);/* Write amt bytes from the pBuf into the file*/int sqliteOsWrite(OsFile *id, const void *pBuf, int amt);
在这里,您可以使用 sqliteOpenReadWrite 函数开始使用文件。此函数为您提供一个指向文件的指针,该文件可用于读取或写入数据。接下来,应该在执行任何读写操作之前获取锁。如果它只是一个读操作,那么应该获取读锁。应该为读和写事务获取写锁。
然后可以通过将文件的偏移量提供给 sqliteOsSeek 函数来查找位置来完成读写。偏移量是从文件的起点到数据应该写入或读取的位置的字节数。
Pager
页是文件系统上数据库事务的最小单位。当数据库需要从文件中读取数据时,它会将它作为一个页面来请求。一旦页面被加载到数据库引擎中,如果它经常访问它的缓存,它就可以存储该页面。页数从一页开始编号。第一个页面称为根页面。页的大小是恒定的。
/*Open pager with the given file name*/int sqlitepager_open(Pager **ppPager,const char *zFilename,int nPage,int nEx);/*Get page specified by the page number*/int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage);/*Start to write data into a page specified in pData*/int sqlitepager_write(void *pData);/*Commit page changes into the file*/int sqlitepager_commit(Pager*);/*Close the connection to the file*/int sqlitepager_close(Pager *pPager);
Btree
Btree 是一种数据结构,用于根据数据的值将数据存储为树。BTree 最简单的形式是二叉树。数据库使用 Btree 数据结构来存储索引以提高数据库的性能。Btree 的每个节点都包含一个用于索引的键列表。可以为表中的每一列创建 Btree 索引。每个 Btree 都有根页面,这是任何 Btree 搜索的起点。
为了指向 Btree 上的一行,使用了称为"Cursor"的特殊指针。游标用于指向一个记录,该记录由页面 id 和偏移量指定,称为 idx。SQLite 将数据库模式存储在称为"master_table"的表上。master_table 总是存储在数据库的第一页上。
了解更多关于SQLite的B树的设计在这个文章。
/*Open file connection to a page file name specified by zFileName with nCache size cache*/int sqliteBtreeOpen(const char *zFilename, int mode, int nCache, Btree **ppBtree)/*Start transaction. This function should called before any btree modification operations*/int sqliteBtreeBeginTrans(Btree *pBt)/*Insert key pKey with nKey byte and value pData with nData byte put into the Btree*/int sqliteBtreeInsert(BtCursor *pCur, const void *pKey, int nKey, const void *pData, int nData)/*Write data into the file*/int sqliteBtreeCommit(Btree *pBt)/*Move cursor to the matching pKey with nKey bytes*/int sqliteBtreeMoveto(BtCursor *pCur, const void *pKey, int nKey, int *pRes)
VDBE(虚拟数据库引擎)
VDBE 是运行一组操作的虚拟机,由代码生成器生成。包括插入、删除、更新、选择在内的所有 SQL 命令都转换成一组操作码,然后在虚拟机上运行。每个操作码包含三个名为 p1、p2 和 p3 的输入。您可以将此输入视为函数的输入。
下面我为以下 SQL 选择语句添加了示例执行操作码堆栈。PC 是程序计数器的指令 ID。 对我来说,SQLite 中最有趣的事情是我可以通过在 SQL 查询的开头附加"explain"关键字来查看给定 SQL 代码的一组 VBDE 操作码指令。
explain select * from foo;
个人电脑 | 操作码 | P1 | P2 | P3 | 评论 |
1 | 列数 | 1 | 0 | 将列数设置为 1 | |
2 | 列名 | 0 | 0 | 价值 | 将列名设置为"值" |
3 | 打开 | 0 | 3 | 富 | 打开光标并将其指向第三页,即 foo 表的根页(p3 不重要 |
4 | 验证Cookies | 46 | 0 | 确保架构未更改 | |
5 | 倒带 | 0 | 11 | 将光标移动到第一个条目 | |
6 | 柱子 | 0 | 0 | 从 Btree 负载读取数据并将其放入堆栈 | |
7 | 柱子 | 0 | 0 | ||
8 | ne | 1 | 10 | 从堆栈中弹出顶部的两个元素。如果它们不相等,则跳转到指令 P2。否则,继续执行下一条指令。 | |
9 | 打回来 | 1 | 0 | 从堆栈中弹出 P1 值并将它们组成一个数组。这将是此 SQL 表达式的结果行 | |
10 | 下一个 | 0 | 5 | 将光标移动到下一条记录,如果数据退出转到 P2 否则转到下一行 | |
11 | 关闭 | 0 | 0 | 关闭光标 |
编译器
Tokenizer、Parser 和 Code Generator 统称为编译器,可生成在 VBDE 上运行的操作码集。Tokenizer 通过扫描 SQL 代码生成一组令牌。然后,它验证语法并生成解析树。Lemon 解析器用于通过预定义的上下文无关语法来解析给定的 SQL 代码。代码生成器将这个解析树转换成一个用 SQLite 操作码编写的小程序。
以上是"SQLite数据库的工作原理分析"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!