SQLite是一款非常受欢迎的数据库,在数据库排行榜上已经进入了前十的行列。这主要是因为数据库很小,可以支持Linux、Windows、iOS、Andriod等主流操作系统。SQLite非常简单,是一个进程内动态库数据库。它最大的特点是可以支持不同的语言,如C、C++、Java等。同时,SQLite也是一个开源数据库,即开发者可以根据自己的需要修改数据的功能特性。SQLite虽然很小,但是功能非常丰富。俗话说“麻雀虽小,五脏俱全”。SQLite不仅具有基本的SQL特性,还具有索引、触发器、视图、事务等特性。SQLiteSQLite的主要API提供了两种访问接口,一种是通过sqlite命令行工具,另一种是通过动态库,即API函数。在学习SQLite架构之前,我们有必要简单介绍一下它的API。其实SQLite的API很简单,主要包括三个函数,分别是sqlite3_open、sqlite3_exec和sqlite3_close。其中,sqlite3_exec是用来执行SQL语句的函数。也就是说sqlite3_exec是sqlite函数的关键入口,我们在后面的分析代码中应该以这个函数作为切入点。其他功能比较简单,不是那么重要。SQLite的整体架构首先,我们从整体架构上介绍一下SQLite。其架构如图所示,包括接口层、SQL命令处理器和存储后端等,核心不是SQLite内核。它包括三部分:接口层、SQL命令处理器和虚拟机。SQL命令处理器负责对用户的SQL进行预处理,最终生成适合虚拟机执行的代码。下面是后端部分,相当于存储引擎。下面我们简单介绍一下各个模块的功能。(1)接口SQLIte库的使用是通过函数调用实现的。为避免与其他库发生冲突,SQLite函数以sqlite3为前缀。接口部分的实现在文件main.c、legacy.c和vdbeapi.c中。其中,main.c包含其主要接口,包括sqlite3_open、sqlite3_config和sqlite3_close等。SQLite中final函数不在main.c中,而是在legacy.c中,里面只包含了这个接口的实现。(2)词法分析器词法分析器对SQL语句字符串进行解析,最终生成一个词序列(tokens)。并将生成的词序列传递给解析器进行下一步。该函数的具体实现在文件tokenize.c中,核心入口函数为sqlite3RunParser。(3)解析器SQLite的解析器是基于Lemon实现的,它实现了将SQL语句字符串解析成语法树。Lemon是一个类似于YACC/BISON的词法分析库。该库的源代码位于工具目录中。(4)代码生成器代码生成器用于生成可在虚拟机上执行的SQL语句对应的代码。代码生成器的实现比较复杂,包含的文件有:build.c、delete.c、attach.c、expr.c、insert.c、pragma.c、select.c、auth.c等。从文件名可以看出,这里的很多文件其实都对应一条SQL语句,比如delete、insert、select等。(5)虚拟机SQL的具体执行是在一个叫做虚拟机的组件中进行的,在前面的架构图中已经展示过了。虚拟机执行的代码是由前面的代码生成器生成的。虚拟机的实现在文件vdbe.h和vdbe.c中。(6)B-treeSQLite的数据是通过B-tree来组织和管理的。每个表或索引都有对应的B树。所有B树都存储在数据库文件中。B树的具体实现在btree.c和btree.h文件中。(7)PagecacheSQLite文件被分成等份,B-tree也以这个大小作为粒度来管理数据。页缓存是粒度对应的内存内容,通过它实现对数据块的读写等访问。页面缓存相关的实现在pager.c和pcache.c等文件中。(8)操作系统接口SQLite是一个跨平台的数据库,其数据存储需要兼容Windows和Linux的文件系统API。为了方便起见,SQLite实现了一个抽象层。这样,对于SQLite的业务逻辑,只需要调用抽象层的接口即可,无需关心操作系统。(9)基础库包含了内存分配、字符串处理等各个模块可能用到的基础库。成分。下面介绍一下数据库文件的相关功能。在SQLite中,一个文件承载着一个数据库实例,这个文件被称为主数据库文件。除了主库文件之外,可能还有一些其他的文件,比如事务的日志文件等,本文主要针对主库文件,其他文件稍后再介绍。(1)页数据库文件由多个页组成,每个页的大小在512到65536字节之间,大小必须是2的幂。页有编号,起始值为1,最大为2到31-2的幂。页的默认大小为4KB。本文以默认尺寸为例进行介绍。数据库中的每一页都有特定的用途,这些用途包括:锁字节页(Lock-bytepage)剩余页B-树页指针映射页payloadoverflowpage数据库文件的第一页是特殊的,它包含整个数据库文件的描述信息,这里称为数据库头信息。(2)数据库头数据库头包含100个字节的内容,各成员的偏移量、大小和作用如下图所示。我们可以创建一个数据库实例,然后将文件内容与数据库头的格式进行对比来理解。例如,数据库头的第一个成员是一个幻数,用于标识该文件是SQLite数据库文件和版本。这个信息可以在下图中找到,可以看出两者完全匹配(SQLiteformat3)。除了上面介绍的数据库头的格式外,每个不同的页面都有不同的布局。
