小巧、快速、可靠。
三选其二。

SQLite 的定制构建

将 SQLite 移植到新的操作系统

1.0 简介

对于大多数应用程序,构建 SQLite 的推荐方法是使用 合并 代码文件 sqlite3.c 及其相应的头文件 sqlite3.h。sqlite3.c 代码文件应该在任何 Unix、Windows 系统上编译和运行,无需任何更改或特殊编译器选项。大多数应用程序可以简单地将 sqlite3.c 文件与其构成应用程序的其他 C 代码文件一起包含,将它们全部编译在一起,并拥有工作且配置良好的 SQLite 版本。

大多数应用程序在 SQLite 的默认配置下运行良好,无需特殊的编译时配置。大多数开发人员应该能够完全忽略本文档,并直接从 合并 构建 SQLite,无需任何特殊知识,也无需采取任何特殊行动。

但是,高度调整和专门的应用程序可能希望或需要用更适合应用程序需求的替代实现来替换 SQLite 的某些内置系统接口。SQLite 的设计目的是在编译时易于重新配置,以满足各个项目的特定需求。SQLite 的编译时配置选项包括:

一般来说,SQLite 中有三个独立的子系统可以在编译时修改或覆盖。互斥锁子系统用于序列化对 SQLite 资源的访问,这些资源在多个线程之间共享。内存分配子系统用于分配 SQLite 对象所需的内存和数据库缓存。最后,虚拟文件系统 子系统用于提供 SQLite 与底层操作系统(尤其是文件系统)之间的可移植接口。我们称这三个子系统为 SQLite 的“接口”子系统。

我们强调大多数应用程序都很好地利用了 SQLite 接口子系统的内置默认实现。鼓励开发人员尽可能使用默认的内置实现,并在没有任何特殊的编译时选项或参数的情况下构建 SQLite。但是,一些高度专门的应用程序可能会从替换或修改这些内置 SQLite 接口子系统中的一个或多个中受益。或者,如果 SQLite 在除 Unix(Linux 或 Mac OS X)、Windows(Win32 或 WinCE)或 OS/2 以外的操作系统上使用,则 SQLite 中内置的任何接口子系统都将无法工作,并且应用程序将需要提供适合目标平台的替代实现。

2.0 配置或替换互斥锁子系统

在多线程环境中,SQLite 使用互斥锁来序列化对共享资源的访问。互斥锁子系统仅适用于从多个线程访问 SQLite 的应用程序。对于单线程应用程序,或仅从单个线程调用 SQLite 的应用程序,可以通过使用以下选项重新编译来完全禁用互斥锁子系统:

-DSQLITE_THREADSAFE=0

互斥锁很便宜,但并非免费,因此当完全禁用互斥锁时,性能会更好。生成的库占用空间也会略小。在编译时禁用互斥锁是适用于有意义的应用程序的推荐优化。

当将 SQLite 作为共享库使用时,应用程序可以测试互斥锁是否已被禁用,方法是使用 sqlite3_threadsafe() API。在运行时链接到 SQLite 并从多个线程使用 SQLite 的应用程序应该检查此 API,以确保它们没有意外地链接到禁用互斥锁的 SQLite 库版本。单线程应用程序当然会正常工作,无论 SQLite 是否配置为线程安全,尽管当使用禁用互斥锁的 SQLite 版本时,它们会稍微快一些。

SQLite 互斥锁也可以使用 sqlite3_config() 接口在运行时禁用。要完全禁用所有互斥锁,应用程序可以调用:

sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);

在运行时禁用互斥锁不如在编译时禁用互斥锁有效,因为 SQLite 仍然必须进行布尔变量测试以查看在每个可能需要互斥锁的位置是否启用了互斥锁。但禁用运行时互斥锁仍然具有性能优势。

对于小心管理线程的多线程应用程序,SQLite 支持一种替代的运行时配置,它介于不使用任何互斥锁和默认情况下在视线范围内互斥锁所有内容之间。这种中间互斥锁对齐可以通过以下方式建立:

sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0);

这里有两个独立的配置更改,可以一起或单独使用。 SQLITE_CONFIG_MULTITHREAD 设置会禁用序列化对 数据库连接 对象和 准备好的语句 对象的访问的互斥锁。使用此设置,应用程序可以自由地从多个线程使用 SQLite,但它必须确保没有两个线程试图同时访问同一个 数据库连接 或与同一个 数据库连接 关联的任何 准备好的语句。两个线程可以同时使用 SQLite,但它们必须使用单独的 数据库连接。第二个 SQLITE_CONFIG_MEMSTATUS 设置会禁用 SQLite 中跟踪所有未决内存分配请求的总大小的机制。这省略了对每个 sqlite3_malloc()sqlite3_free() 调用进行互斥锁的需要,这节省了大量的互斥锁操作。但禁用内存统计机制的后果是 sqlite3_memory_used()sqlite3_memory_highwater()sqlite3_soft_heap_limit64() 接口将停止工作。

SQLite 使用 pthreads 来实现其在 Unix 上的互斥锁,并且 SQLite 需要递归互斥锁。大多数现代 pthread 实现都支持递归互斥锁,但并非所有都支持。对于不支持递归互斥锁的系统,建议应用程序仅在单线程模式下运行。如果这不可行,SQLite 提供了一个基于 pthreads 的标准“快速”互斥锁构建的替代递归互斥锁实现。只要 pthread_equal() 是原子的并且处理器具有连贯的数据缓存,这种替代实现应该可以正常工作。可以通过以下编译器命令行开关启用替代递归互斥锁实现:

-DSQLITE_HOMEGROWN_RECURSIVE_MUTEX=1

将 SQLite 移植到新的操作系统时,通常需要用基于新操作系统互斥锁原语构建的替代实现来完全替换内置的互斥锁子系统。这可以通过使用以下选项编译 SQLite 来实现:

-DSQLITE_MUTEX_APPDEF=1

当 SQLite 使用 SQLITE_MUTEX_APPDEF=1 选项编译时,它会完全省略其 互斥锁原语函数 的实现。但 SQLite 库仍然尝试在必要时调用这些函数,因此应用程序必须自己实现 互斥锁原语函数 并将它们与 SQLite 链接在一起。

3.0 配置或替换内存分配子系统

默认情况下,SQLite 从标准库的 malloc()/free() 实现获取对象和缓存所需的内存。还有一些正在进行的实验性内存分配器工作,它们满足来自应用程序启动时传递给 SQLite 的单个固定内存缓冲区的内存请求。有关这些实验性内存分配器的更多信息将在本文档的未来版本中提供。

SQLite 支持应用程序通过在 sqlite3_mem_methods 对象的实例中填充替代实现例程的指针,然后使用 sqlite3_config() 接口注册新的替代实现来指定替代内存分配器。例如:

sqlite3_config(SQLITE_CONFIG_MALLOC, &my_malloc_implementation);

SQLite 会复制 sqlite3_mem_methods 对象的内容,因此可以在 sqlite3_config() 调用返回后修改该对象。

4.0 添加新的虚拟文件系统

版本 3.5.0(2007-09-04)开始,SQLite 支持一个名为 虚拟文件系统 或“VFS”的接口。该对象的名字有点误导人,因为它实际上是整个底层操作系统的接口,而不仅仅是文件系统。

VFS 接口的一个有趣的功能是 SQLite 可以同时支持多个 VFS。每个 数据库连接 都必须在使用 sqlite3_open_v2() 首次打开连接时为其使用选择一个 VFS。但如果进程包含多个 数据库连接,则每个连接都可以选择不同的 VFS。可以使用 sqlite3_vfs_register() 接口在运行时添加 VFS。

SQLite 在 Unix、Windows 和 OS/2 上的默认构建包含适合目标平台的 VFS。SQLite 针对其他操作系统的构建默认情况下不包含 VFS,但应用程序可以在运行时注册一个或多个 VFS。

5.0 将 SQLite 移植到新的操作系统

为了将 SQLite 移植到新的操作系统(默认情况下不受支持的操作系统),应用程序必须提供:

所有这些都可以在一个单独的辅助 C 代码文件中提供,然后与库存的“sqlite3.c”代码文件链接,以生成适合目标操作系统的 SQLite 构建。除了替代的互斥锁和内存分配子系统以及新的 VFS 之外,辅助 C 代码文件还应包含以下两个例程的实现:

“sqlite3.c”代码文件包含适合 Unix、Windows 和 OS/2 的 VFS 以及 sqlite3_initialize()sqlite3_shutdown() 函数的默认实现。要防止在编译 sqlite3.c 时加载这些默认组件之一,必须添加以下编译时选项:

-DSQLITE_OS_OTHER=1

SQLite 核心将在早期调用 sqlite3_initialize()。辅助 C 代码文件可以包含 sqlite3_initialize() 的实现,该实现注册合适的 VFS,并且可能还会初始化备用互斥锁系统(如果需要互斥锁)或执行所需的任何内存分配子系统初始化。SQLite 核心从不调用 sqlite3_shutdown(),但它是官方 SQLite API 的一部分,并且在使用 -DSQLITE_OS_OTHER=1 编译时不会以其他方式提供,因此辅助 C 代码文件应该为了完整性而提供它。