本文档描述了 SQLite 库的架构。此信息对那些想要了解或修改 SQLite 内部工作原理的人很有用。
附近的图表显示了 SQLite 的主要组件以及它们如何交互。下面的文字解释了各个组件的作用。
SQLite 的工作原理是将 SQL 文本编译成字节码,然后使用虚拟机运行该字节码。
sqlite3_prepare_v2() 及其相关接口充当编译器,用于将 SQL 文本转换为字节码。sqlite3_stmt 对象是单个字节码程序的容器,该程序实现单个 SQL 语句。sqlite3_step() 接口将字节码程序传递到虚拟机,并运行该程序,直到它完成、形成要返回的结果行、遇到致命错误或被中断。
大部分C 语言接口位于源文件main.c、legacy.c 和 vdbeapi.c 中,尽管一些例程分散在其他文件中,以便它们可以访问具有文件范围的数据结构。sqlite3_get_table() 例程在table.c 中实现。sqlite3_mprintf() 例程位于printf.c 中。sqlite3_complete() 接口位于complete.c 中。TCL 接口 由tclsqlite.c 实现。
为了避免名称冲突,SQLite 库中的所有外部符号都以前缀sqlite3开头。那些用于外部使用的符号(换句话说,构成 SQLite API 的符号)会添加下划线,因此以sqlite3_开头。扩展 API 有时会在下划线之前添加扩展名称;例如:sqlite3rbu_ 或 sqlite3session_。
当要评估包含 SQL 语句的字符串时,它首先被发送到词法分析器。词法分析器将 SQL 文本分解成标记,并将这些标记逐个传递给解析器。词法分析器在文件中手动编码
请注意,在此设计中,词法分析器调用解析器。熟悉 YACC 和 BISON 的人可能习惯于以相反的方式做事——让解析器调用词法分析器。但是,让词法分析器调用解析器更好,因为它可以实现线程安全并且运行速度更快。
解析器根据标记的上下文为其分配含义。SQLite 的解析器是使用Lemon 解析器生成器生成的。Lemon 执行与 YACC/BISON 相同的工作,但它使用不同的输入语法,这种语法不易出错。Lemon 还会生成一个可重入且线程安全的解析器。并且 Lemon 定义了非终结符析构函数的概念,以便在遇到语法错误时不会泄漏内存。驱动 Lemon 并定义 SQLite 理解的 SQL 语言的语法文件位于parse.y 中。
由于 Lemon 通常不是开发机器上的程序,因此 Lemon 的完整源代码(只有一个 C 文件)包含在 SQLite 发行版的“tool”子目录中。
解析器将标记组装成语法树后,代码生成器运行以分析语法树并生成执行 SQL 语句工作的字节码。预处理语句对象是此字节码的容器。代码生成器中有许多文件,包括:attach.c、auth.c、build.c、delete.c、expr.c、insert.c、pragma.c、select.c、trigger.c、update.c、vacuum.c、where.c、wherecode.c 和 whereexpr.c。在这些文件中,大部分重要的魔法都在这里发生。expr.c 处理表达式的代码生成。where*.c 处理 SELECT、UPDATE 和 DELETE 语句中 WHERE 子句的代码生成。文件 attach.c、delete.c、insert.c、select.c、trigger.c update.c 和 vacuum.c 处理具有相同名称的 SQL 语句的代码生成。(每个文件都会根据需要调用expr.c 和where.c 中的例程。)所有其他 SQL 语句都由build.c 编码。auth.c 文件实现了sqlite3_set_authorizer() 的功能。
代码生成器,尤其是where*.c 和select.c 中的逻辑,有时称为查询计划器。对于任何特定的 SQL 语句,可能存在数百、数千甚至数百万种不同的算法来计算答案。查询计划器是一种 AI,它努力从这数百万个选择中选择最佳算法。
代码生成器创建的字节码程序由虚拟机运行。
虚拟机本身完全包含在一个源文件中vdbe.c 中。vdbe.h 头文件定义了虚拟机与 SQLite 库其余部分之间的接口,而vdbeInt.h 定义了对虚拟机本身私有的结构和接口。各种其他vdbe*.c 文件是虚拟机的助手。vdbeaux.c 文件包含虚拟机使用的实用程序和库其余部分用于构建 VM 程序的接口模块。vdbeapi.c 文件包含对虚拟机的外部接口,例如sqlite3_bind_int() 和sqlite3_step()。各个值(字符串、整数、浮点数和 BLOB)存储在一个名为“Mem”的内部对象中,该对象由vdbemem.c 实现。
SQLite 使用对 C 语言例程的回调来实现 SQL 函数。即使是内置的 SQL 函数也是这样实现的。大多数内置 SQL 函数(例如:abs()、count()、substr() 等)可以在func.c 源文件中找到。日期和时间转换函数位于date.c 中。一些函数,如coalesce() 和typeof(),由代码生成器直接作为字节码实现。
SQLite 数据库使用在btree.c 源文件中找到的 B 树实现维护在磁盘上。数据库中的每个表和每个索引都使用单独的 B 树。所有 B 树都存储在同一个磁盘文件中。文件格式详细信息是稳定且定义明确的,并且保证将来兼容。
B 树子系统与 SQLite 库其余部分的接口由头文件btree.h 定义。
B 树模块以固定大小的页面请求磁盘上的信息。默认page_size 为 4096 字节,但可以是 512 到 65536 字节之间的任何 2 的幂。页面缓存负责读取、写入和缓存这些页面。页面缓存还提供回滚和原子提交抽象,并负责数据库文件的锁定。B 树驱动程序从页面缓存请求特定页面,并在想要修改页面或提交或回滚更改时通知页面缓存。页面缓存处理确保快速、安全和高效地处理请求的所有复杂细节。
主要的页面缓存实现位于pager.c 文件中。WAL 模式逻辑位于单独的wal.c 中。内存缓存由pcache.c 和pcache1.c 文件实现。页面缓存子系统与 SQLite 其余部分之间的接口由头文件pager.h 定义。
为了提供跨操作系统的可移植性,SQLite 使用一个称为VFS 的抽象对象。每个 VFS 都提供用于打开、读取、写入和关闭磁盘上的文件以及执行其他特定于操作系统的任务的方法,例如查找当前时间或获取随机数以初始化内置伪随机数生成器。SQLite 目前为 unix(在os_unix.c 文件中)和 Windows(在os_win.c 文件中)提供 VFS。
内存分配、不区分大小写的字符串比较例程、可移植的文本到数字转换例程和其他实用程序位于util.c 中。解析器使用的符号表由hash.c 中找到的哈希表维护。utf.c 源文件包含 Unicode 转换子例程。SQLite 在printf.c 中有自己的私有printf() 实现(带有一些扩展),并在random.c 中有自己的伪随机数生成器 (PRNG)。
源代码树“src/”文件夹中名称以test 开头的文件仅用于测试,不包含在库的标准构建中。