SQLite 的架构

介绍

本文档描述了 SQLite 库的体系结构。此处的信息对那些想要了解或修改 SQLite 内部工作原理的人很有用。

附近的图表显示了 SQLite 的主要组件以及它们如何互操作。下面的文字解释了各个组件的作用。

概述

SQLite 的工作原理是将 SQL 文本编译成字节码,然后使用虚拟机运行该字节码。

sqlite3_prepare_v2()和相关接口充当将 SQL 文本转换为字节码的编译器sqlite3_stmt对象是实现单个 SQL 语句的单个字节码程序的容器sqlite3_step()接口将一个字节码程序传递到虚拟机中,并运行该程序直到它完成,或者形成一行要返回的结果,或者遇到致命错误,或者被 中断

界面

许多C 语言接口都可以在源文件main.clegacy.cvdbeapi.c中找到, 尽管一些例程分散在其他文件中,它们可以在这些文件中访问具有文件范围的数据结构。sqlite3_get_table()例程在table.c实现。sqlite3_mprintf()例程位于printf.csqlite3_complete()接口在complete.cTCL 接口tclsqlite.c实现

为避免名称冲突,SQLite 库中的所有外部符号都以前缀sqlite3开头那些供外部使用的符号(换句话说,那些构成 SQLite API 的符号)添加下划线,因此以sqlite3_开头。扩展 API 有时会在下划线之前添加扩展名称;例如: sqlite3rbu_sqlite3session_

分词器

当要评估包含 SQL 语句的字符串时,它首先被发送到分词器。标记器将 SQL 文本分解为标记,并将这些标记一个一个地交给解析器。分词器在文件中手动编码标记化.c.

请注意,在此设计中,分词器调用解析器。熟悉 YACC 和 BISON 的人可能习惯于以相反的方式做事——让解析器调用分词器。不过,让分词器调用解析器会更好,因为它可以成为线程安全的并且运行速度更快。

解析器

解析器根据上下文为标记分配含义。SQLite 的解析器是使用 Lemon 解析器生成器生成的。Lemon 与 YACC/BISON 的工作相同,但它使用不同的输入语法,不易出错。Lemon 还生成一个可重入且线程安全的解析器。并且 Lemon 定义了非终结符析构函数的概念,这样在遇到语法错误时不会泄漏内存。在parse.y中可以找到驱动 Lemon 并定义 SQLite 理解的 SQL 语言的语法文件

因为 Lemon 是一个通常不会在开发机器上找到的程序,所以 Lemon 的完整源代码(只有一个 C 文件)包含在 SQLite 分发版的“tool”子目录中。

代码生成器

解析器将标记组装成解析树后,代码生成器运行以分析解析树并生成 执行 SQL 语句工作的字节码。准备好的语句对象是这个字节码的容器代码生成器中有很多文件,包括: attach.cauth.cbuild.cdelete.cexpr.cinsert.cpragma.cselect.ctrigger.cupdate.cvacuum.cwhere.cwherecode.cwhereexpr.c. 在这些文件中,大多数重要的魔法都发生了。 expr.c处理表达式的代码生成。 where*.c处理 SELECT、UPDATE 和 DELETE 语句上 WHERE 子句的代码生成。文件attach.cdelete.cinsert.cselect.ctrigger.c update.cvacuum.c处理同名 SQL 语句的代码生成。(这些文件中的每一个都根据需要调用expr.cwhere.c中的例程。)所有其他 SQL 语句都从build.c中编码出来。权威.c文件实现了 sqlite3_set_authorizer()的功能。

代码生成器,尤其是where*.cselect.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()等一些函数直接由代码生成器实现为字节码。

B树

SQLite 数据库使用btree.c源文件中的 B 树实现在磁盘上维护。单独的 B 树用于数据库中的每个表和每个索引。所有的 B 树都存储在同一个磁盘文件中。文件格式细节稳定且定义明确,并保证向前兼容。

B 树子系统和 SQLite 库的其余部分的接口由头文件btree.h定义。

页面缓存

B 树模块以固定大小的页面从磁盘请求信息。默认的page_size是 4096 字节,但可以是 512 到 65536 字节之间的任何 2 的幂。页缓存负责读取、写入和缓存这些页。页面缓存还提供回滚和原子提交抽象并负责数据库文件的锁定。B 树驱动程序从页面缓存中请求特定页面,并在需要修改页面或提交或回滚更改时通知页面缓存。页面缓存处理所有杂乱的细节,以确保请求得到快速、安全和高效的处理。

主要页面缓存实现在 pager.c文件中。 WAL 模式逻辑在单独的 wal.c中。内存缓存由 pcache.cpcache1.c文件实现。页面缓存子系统和 SQLite 其余部分之间的接口由头文件pager.h定义。

操作系统接口

为了提供跨操作系统的可移植性,SQLite 使用一个称为VFS的抽象对象。每个 VFS 都提供了打开、读取、写入和关闭磁盘文件的方法,以及其他特定于操作系统的任务,例如查找当前时间或获取随机数以初始化内置伪随机数生成器。SQLite 目前为 unix(在os_unix.c 文件中)和 Windows(在os_win.c文件中)提供 VFSes。

公用事业

内存分配、不区分大小写的字符串比较例程、可移植的文本到数字转换例程和其他实用程序位于util.c中。解析器使用的符号表由hash.c中的哈希表维护utf.c文件包含 Unicode 转换子程序。SQLite 在printf.c中有自己的私有 printf()实现(带有一些扩展),在random.c中有自己的伪随机数生成器 (PRNG)

测试代码

源代码树的“src/”文件夹中名称以test 开头的文件 仅用于测试,不包含在库的标准构建中。