SQLite 是一个将应用程序生成的较高层级磁盘 I/O 请求转换为操作系统可以执行的较低层级 I/O 操作的软件库。应用程序使用SQL 语言构建高层级 I/O 请求。SQLite 将每个高层级 SQL 语句转换为一系列许多低层级 I/O 请求(打开文件、从文件中读取几个字节、将几个字节写入文件等),从而完成 SQL 请求的工作。
应用程序可以通过直接调用操作系统 I/O 例程或使用键值存储引擎(例如Berkeley DB或RocksDB(仅举两例))来完成所有磁盘 I/O 操作。但是,使用基于 SQL 语言的更高级别的接口有一些优势。
SQL 是一种非常高级的语言。几行 SQL 可以替换数百甚至数千行过程代码。因此,SQL 减少了开发和维护应用程序所需的工作量,从而有助于减少应用程序中的错误数量。
SQL 和 SQLite 是事务性的。使用事务性存储系统可以更容易地推断应用程序的行为,并编写健壮的应用程序,即使在软件错误、硬件故障或电源丢失的情况下也能正常运行。
SQLite 通常比直接低层级 I/O 更快。这违反直觉。人们会认为像 SQLite 这样的高级接口会带来运行时开销。从理论上讲,这是正确的。但在实践中,像 SQLite 这样的基于 SQL 的系统执行了许多幕后优化,应用程序开发人员永远没有时间创建和维护这些优化,因此基于 SQL 的系统最终提供了净性能提升。
除了 SQLite 之外,还有许多基于 SQL 的数据库管理系统可用。常见选项包括 MySQL、PostgreSQL 和 SQL Server。所有这些系统都使用 SQL 语言与应用程序通信,就像 SQLite 一样。但是这些其他系统在重要方面与 SQLite 不同。
SQLite 是一个无服务器软件库,而其他系统则是基于客户端-服务器的。对于 MySQL、PostgreSQL、SQL Server 等,应用程序会发送包含一些 SQL 的消息到一个单独的服务器线程或进程。该单独的线程或进程执行请求的 I/O,然后将结果发送回应用程序。但是 SQLite 没有单独的线程或进程。SQLite 在与应用程序相同的地址空间中运行,使用相同的程序计数器和堆存储。SQLite 不进行进程间通信 (IPC)。当应用程序将 SQL 语句发送到 SQLite(通过调用相应的 SQLite 库子例程)时,SQLite 在与调用方相同的线程中解释 SQL。当 SQLite API 例程返回时,它不会留下任何与应用程序分开运行的后台任务。
SQLite 数据库是磁盘上的单个普通文件(具有定义明确的文件格式)。对于其他系统,“数据库”通常是隐藏在文件系统模糊目录中的大量单独文件,甚至分布在多台机器上。但是对于 SQLite,完整的数据库只是一个普通的磁盘文件。
理解 SQL 数据库引擎工作原理的最佳方法是将 SQL 视为一种编程语言,而不是“查询语言”。每个 SQL 语句都是一个单独的程序。应用程序构建 SQL 程序源文件并将其发送到数据库引擎。数据库引擎将 SQL 源代码编译成可执行格式,运行该可执行文件,然后将结果发送回应用程序。
虽然 SQL 是一种编程语言,但它与其他编程语言(如 C、Javascript、Python 或 Go)不同,因为 SQL 是一种声明式语言,而其他语言是命令式语言。这是一个重要的区别,对用于将程序源文本转换为可执行格式的编译器的设计有影响。但是,这些细节不应该掩盖 SQL 实际上只是一种编程语言的事实。
所有编程语言都分两步处理
将程序源文本转换为可执行格式。
运行上一步生成的执行文件以执行所需的操作。
所有编程语言都使用这两个基本步骤。主要区别在于可执行格式。
像 C++ 和 Rust 这样的“编译”语言将源文本转换为底层硬件可以直接执行的机器代码。存在对 SQL 执行同样操作的 SQL 数据库系统——它们将每个 SQL 语句直接转换为机器代码。但这种方法并不常见,也不是 SQLite 使用的方法。
其他语言(如 Java、Perl、Python 和 TCL)通常将程序源文本转换为字节码。然后,此字节码通过一个解释器运行,该解释器读取字节码并执行所需的操作。SQLite 使用这种字节码方法。如果在 SQLite 中的任何 SQL 语句前面加上“EXPLAIN”关键字,它将显示生成的字节码,而不是运行字节码。
另一种方法是将程序源文本转换为内存中的对象树。这棵树是“可执行文件”。一个解释器通过遍历树来运行可执行文件。这是 MySQL、PostgreSQL 和 SQL Server 使用的技术。
当然,并非每种语言都完全适合上述类别之一。这适用于 SQL 数据库引擎和更熟悉的命令式编程语言。Javascript 以使用混合执行模型而闻名,其中代码最初编译成对象树,但可能会进一步转换为(使用即时编译)更有效的字节码或机器代码,作为提高性能的一种手段。
可执行格式最终只是一个实现细节。关键点是,所有语言都有一个编译器步骤,将程序转换为可执行格式,以及一个运行步骤,执行编译后的程序。
当 SQL 程序提交到 SQLite 时,第一步是将源文本拆分为“标记”。标记可能是
空格和注释标记会被丢弃。所有其他标记都会被送入一个LALR(1) 解析器,该解析器分析输入程序的结构并为输入程序生成一个抽象语法树 (AST)。
解析器将 AST 传递给代码生成器。代码生成器是 SQLite 的核心,也是大多数魔法发生的地方。代码生成器解析 AST 中的符号名称——将输入 SQL 中的列和表名称与数据库的实际列和表匹配。代码生成器还对 AST 进行各种转换以“优化”它。最后,代码生成器选择合适的算法来实现 AST 请求的操作,并构建字节码来执行这些操作。
代码生成器生成的字节码称为“预编译语句”。将 SQL 源文本转换为预编译语句类似于通过调用 gcc 或 clang 将 C++ 程序转换为机器代码。人类可读的源文本(SQL 或 C++)输入,机器可读的可执行文件(字节码或机器代码)输出。
原子提交文档描述了 SQLite 如何实现事务。
字节码引擎文档包含有关 SQLite 使用的字节码格式以及如何查看和解释 SQLite 预编译语句的更多信息。
SQLite 查询计划器和下一代查询计划器文档提供了有关 SQLite 用于实现 SQL 语句的算法以及如何选择每个单独 SQL 语句的合适算法的更多详细信息。
此页面上次修改于 2022-06-16 15:42:19 UTC