介绍
这篇文档描述了SQLite库的架构。如果想理解SQLite内部原理,或者对它进行修改,这篇文档提供了有帮助的信息。
下图展示了SQLite的主要组件和它们的协作方式。后文解释了这些组件的具体职责。
本文中各组件的原文译文对照:
原文 | 译文 |
---|---|
Interface | 接口 |
SQL Command Processor | SQL命令处理器 |
Virtual Machine | 虚拟机 |
Tokenizer | 词法分析器 |
Parser | 语法分析器 |
B-Tree | B树 |
Pager | 页缓存 |
OS Interface | OS接口 |
Utilities | 通用库 |
Test Code | 测试代码 |
概览
SQLite的工作流程是将SQL文本编译成字节码,然后在虚拟机上运行这些字节码。
sqlite3_prepare_v2()和相关的接口起到编译器的作用,将SQL文本转化为字节码。sqlite3_stmt对象是一个字节码容器,存储了单条SQL语句所编译出的字节码。sqlite3_step()接口将字节码程序传给虚拟机,执行字节码程序直到它完成、返回结果集、遇到严重错误或被中断(interrupted)。
接口
C语言接口主要在main.c,legacy.c 和 vdbeapi.c中,也有一些接口分散在其他文件中,这是为了使它们能使用一些文件私有的数据结构。sqlite3_get_table()例程在table.c中,sqlite3_mprintf()在printf.c中,sqlite3_complete()在tokenize.c中,TCL Interface在tclsqlite.c中。
为避免名称冲突,SQLite库中的所有外部符号都以前缀sqlite3开头。那些用于外部使用的符号,也就是出现在SQLite API中的符号,会添加一个下划线,从而以sqlite3_开头。扩展API有时会在下划线之前添加扩展名,例如:sqlite3rbu_或sqlite3session_。
词法分析器
当一条SQL语句要被编译时,它首先被发送给词法分析器。
词法分析器将SQL文本分解成词,然后将它一个个交给语法分析器。词法分析器的文件是tokenize.c,其中的代码是手工写的。
注意此处的设计,是词法分析器调用语法分析器。熟悉YACC或者BISON的人可能习惯于反过来——让语法分析器调用词法分析器。前者更好,因为它可以做到线程安全,并且运行地更快。
语法分析器
语法分析器根据词的上下文赋予它语义。
SQLite的语法分析器通过Lemon LALR(1)生成。Lemon的功能和YACC、BISON类似,但是它所使用的输入语法更不容易出错。同时Lemon生成的语法分析器是可重入和线程安全的。Lemon定义了“非终结符析构器(non-terminal destructor)”的概念,因此它在遇到语法错误时不会泄露内存。Lemon所需的语法文件是parse.y,其中定义了SQLite使用的SQL语法。
因为Lemon很可能不在开发机器上,它完整的源代码在SQLite的tool文件夹中,仅有一个C文件。
代码生成器
语法分析器将词转化为语法树,代码生成器分析语法树,并且生成符合SQL功能的字节码。
prepared statement对象是这段字节码的容器。代码生成器包含很多文件: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**。SQLite的魔法大多发生在这些文件中。
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中的逻辑部分,有时被称作查询计划器query planner。对于任何一条SQL语句,可能有成百上千种不同的算法能得出结果。查询计划器是一个AI,尽力从其中选出最好的算法。
字节码引擎
生成器输出的代码最终由虚拟机来执行。
虚拟机本身完全包含在单个文件中,即vdbe.c文件。vdbe.h头文件定义了其他SQLite库与虚拟机之间的接口,而vdbeInt.h定义了虚拟机私有的数据结构和接口。
剩下的多个vdbe.c文件是虚拟机的辅助代码。vdbeaux.c文件包含了虚拟机使用的通用库,以及其他库用来构造VM程序的接口模块。vdbeapi.c文件包含了虚拟机的外部接口,比如sqlite3_bind_int()和 sqlite3_step()。单个值(字符串,整数,浮点数,二进制数据)存储在一个内部对象“Mem”中,该对象在vdbemem.c*实现。
SQLite通过回调C语言来实现SQL的函数。甚至内置的SQL函数也通过这种方式实现。大部分内置SQL函数,例如abs(), count(),substr()等,都写在func.c文件中。日期时间转换函数写在date.c中。一些函数,例如coalesce() 和 typeof()直接被代码生成器实现为字节码。
B树
SQLite使用B树结构来维护磁盘上的数据,B树的实现在btree.c文件中。
数据库中每个表和索引都使用单独的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中。
OS接口
为了提供跨操作系统移植性,SQLite使用名为VFS的抽象对象。每个VFS提供对磁盘文件的打开、关闭以及读写方法,以及其他特定于操作系统的功能,如获得当前时间,为伪随机数生成器设置随机种子等。
SQLite目前对unix和Windows提供VFS,分别在os_unix.c和os_win.c文件中。
通用类
内存分配,大小写不敏感的字符串比较例程,可移植的字符串到数字转换例程,以及其他通用库位于util.c中。
hash.c实现了哈希表结构,在语法解析器中被用作符号表。utf.c源文件包含Unicode转换的相关例程。在printf.c中,SQLite实现了自己私有的printf(),包含了一些拓展。在random.c中,SQLite也实现了自己的伪随机数生成器。
测试代码
在src文件夹中,以test开头的文件为测试代码,不被包含在标准库版本中。