小巧。快速。可靠。
三选二。
SQLite 操作系统接口或“VFS”

1. 简介

本文介绍了 SQLite 操作系统可移植性层或“VFS”——位于 SQLite 实现堆栈底部的模块,它提供了跨操作系统的可移植性。

2. VFS 与 SQLite 其他部分的关系

SQLite 库的内部组织可以看作是右侧所示的模块堆栈。分词器、解析器和代码生成器组件用于处理 SQL 语句,并将其转换为虚拟机语言或字节码中的可执行程序。粗略地说,这三个顶层实现了 sqlite3_prepare_v2()。由前三个层生成的字节码是 预处理语句。虚拟机模块负责运行 SQL 语句字节码。B 树模块将数据库文件组织成多个具有有序键和对数性能的键值存储。Pager 模块负责将数据库文件的页面加载到内存中,实现和控制事务,以及创建和维护防止数据库在崩溃或电源故障后损坏的日志文件。操作系统接口是一个薄抽象层,提供了一组通用的例程,用于使 SQLite 能够在不同的操作系统上运行。粗略地说,底部的四个层实现了 sqlite3_step()

本文讨论的是底层。

操作系统接口——也称为“VFS”——是 SQLite 能够跨操作系统移植的原因。每当 SQLite 中的任何其他模块需要与操作系统通信时,它们都会调用 VFS 中的方法。然后,VFS 调用满足请求所需的特定于操作系统的代码。因此,将 SQLite 移植到新的操作系统只需编写新的操作系统接口层或“VFS”。

3. 多个 VFS

标准 SQLite 源代码树包含用于 unix 和 windows 的内置 VFS。可以使用 sqlite3_vfs_register() 接口在启动时或运行时添加备用 VFS。

可以同时注册多个 VFS。每个 VFS 都有一个唯一的名称。同一进程内的独立 数据库连接 可以同时使用不同的 VFS。就此而言,如果单个数据库连接使用 ATTACH 命令打开了多个数据库文件,则每个附加的数据库可能都使用不同的 VFS。

3.1. 标准 Unix VFS

Unix 版本附带多个内置 VFS。unix 的默认 VFS 称为“unix”,在大多数应用程序中使用。其他可能在 unix 中找到的 VFS(取决于编译时选项)包括

  1. unix-dotfile - 使用点文件锁定而不是 POSIX 建议锁定。

  2. unix-excl - 获取并持有数据库文件的独占锁,防止其他进程访问数据库。还将 wal-index 保存在堆中而不是共享内存中。

  3. unix-none - 所有文件锁定操作都是空操作。

  4. unix-namedsem - 使用命名信号量进行文件锁定。仅限 VXWorks。

各种 unix VFS 仅在处理文件锁定方面有所不同——它们的大部分实现与彼此共享,并且都位于同一个 SQLite 源文件中:os_unix.c。请注意,除了“unix”和“unix-excl”之外,各种 unix VFS 都使用不兼容的锁定实现。如果两个进程使用不同的 unix VFS 访问同一个 SQLite 数据库,它们可能无法看到彼此的锁,并可能最终相互干扰,导致数据库损坏。“unix-none”VFS 特别是根本不进行锁定,如果同时由两个或多个数据库连接使用,则很容易导致数据库损坏。鼓励程序员仅使用“unix”或“unix-excl”,除非有令人信服的理由这样做。

3.2. 标准 Windows VFS

Windows 版本也附带多个内置 VFS。默认的 Windows VFS 称为“win32”,在大多数应用程序中使用。其他可能在 Windows 版本中找到的 VFS 包括

  1. win32-longpath - 类似于“win32”,但路径名长度可以达到 65534 字节,而“win32”中的路径名最大为 1040 字节。

  2. win32-none - 所有文件锁定操作都是空操作。

  3. win32-longpath-none - “win32-longpath”和“win32-none”的组合——支持长路径名,所有锁定操作都是空操作。

与 unix 一样,各种 Windows VFS 的大部分代码都是共享的。

3.3. 指定使用哪个 VFS

始终存在一个 VFS 作为默认 VFS。在 unix 系统上,“unix”VFS 作为默认值出现,在 windows 上为“win32”。如果未采取其他操作,新的数据库连接将使用默认 VFS。

可以使用 sqlite3_vfs_register() 接口(第二个参数为 1)注册或重新注册 VFS 来更改默认 VFS。因此,如果(unix)进程希望始终使用“unix-nolock”VFS 代替“unix”,则以下代码将起作用

sqlite3_vfs_register(sqlite3_vfs_find("unix-nolock"), 1);

备用 VFS 也可以指定为 sqlite3_open_v2() 函数的第四个参数。例如

int rc = sqlite3_open_v2("demo.db", &db, SQLITE_OPEN_READWRITE, "unix-nolock");

最后,如果启用了 URI 文件名,则可以使用 URI 上的“vfs=”参数指定备用 VFS。此技术适用于 sqlite3_open()sqlite3_open16()sqlite3_open_v2(),以及当新数据库 ATTACH 到现有数据库连接时。例如

ATTACH 'file:demo2.db?vfs=unix-none' AS demo2;

由 URI 指定的 VFS 具有最高优先级。接下来是作为 sqlite3_open_v2() 的第四个参数指定的 VFS。如果未另行指定 VFS,则使用默认 VFS。

3.4. VFS 适配器

从 SQLite 堆栈上层来看,每个打开的数据库文件都使用一个 VFS。但在实践中,特定 VFS 可能只是围绕执行实际工作的另一个 VFS 的一个薄包装器。我们将包装器 VFS 称为“适配器”。

适配器的一个简单示例是“vfstrace”VFS。这是一个 VFS(在 test_vfstrace.c 源文件中实现),它将与每个 VFS 方法调用关联的消息写入日志文件,然后将控制权传递给另一个 VFS 以执行实际工作。

3.5. 其他 VFS 示例

以下是公共 SQLite 源代码树中可用的其他 VFS 实现

核心 SQLite 源代码库和可用扩展中还有其他 VFS 实现。以上列表并非旨在详尽无遗,而仅仅是代表可以使用 VFS 接口实现的各种功能。

4. VFS 实现

新的 VFS 是通过对三个对象进行子类化来实现的

一个 sqlite3_vfs 对象定义了 VFS 的名称以及实现与操作系统接口的核心方法,例如检查文件是否存在、删除文件、创建文件以及打开文件以进行读写,以及将文件名转换为规范形式。 sqlite3_vfs 对象还包含从操作系统获取随机数、挂起进程(睡眠)以及查找当前日期和时间的方法。

sqlite3_file 对象表示一个打开的文件。 sqlite3_vfs 的 xOpen 方法在打开文件时构造一个 sqlite3_file 对象。 sqlite3_file 在文件打开期间跟踪文件的状态。

sqlite3_io_methods 对象保存用于与打开的文件交互的方法。每个 sqlite3_file 都包含一个指向 sqlite3_io_methods 对象的指针,该对象适合其表示的文件。 sqlite3_io_methods 对象包含执行以下操作的方法:从文件读取和写入、截断文件、将任何更改刷新到持久存储、查找文件大小、锁定和解锁文件以及关闭文件并销毁 sqlite3_file 对象。

编写新 VFS 的代码涉及为 sqlite3_vfs 对象构造一个子类,然后使用对 sqlite3_vfs_register() 的调用来注册该 VFS 对象。VFS 实现还为 sqlite3_filesqlite3_io_methods 提供子类,但这些对象不会直接向 SQLite 注册。相反, sqlite3_file 对象从 sqlite3_vfs 的 xOpen 方法返回,并且 sqlite3_file 对象指向 sqlite3_io_methods 对象的实例。

此页面上次修改于 2024-02-22 15:53:45 UTC