注意: 使用 WAL 模式 并且将 PRAGMA synchronous 设置为 NORMAL 可以避免在事务提交期间调用 fsync(),并且仅在 检查点 操作期间调用 fsync()。使用 WAL 模式 在很大程度上避免了对该异步 I/O 模块的需求。因此,不再支持此模块。源代码继续存在于 SQLite 源代码树中,但它不是任何标准构建的一部分,并且不再维护。保留此文档以供历史参考。
通常,当 SQLite 写入数据库文件时,它会等待写入操作完成,然后才将控制权返回给调用应用程序。由于写入文件系统通常比 CPU 绑定操作慢得多,因此这可能成为性能瓶颈。异步 I/O 后端是一个扩展,它导致 SQLite 使用在后台运行的单独线程执行所有写入请求。尽管这不会减少整体系统资源(CPU、磁盘带宽等),但它确实允许 SQLite 即使在写入数据库时也能快速将控制权返回给调用方。
使用异步 I/O,写入请求由在后台运行的单独线程处理。这意味着启动数据库写入的线程不必等待(有时很慢的)磁盘 I/O 完成。写入似乎很快发生,但实际上它在后台以其通常的缓慢速度发生。
异步 I/O 似乎提供了更好的响应能力,但代价是牺牲了持久性。使用 SQLite 的默认 I/O 后端,一旦写入完成,您就知道您写入的信息已安全地保存在磁盘上。使用异步 I/O,情况并非如此。如果您的程序崩溃或在数据库写入之后但在异步写入线程完成之前发生断电,则数据库更改可能永远不会写入磁盘,并且数据库的下一个用户可能看不到您的更改。
使用异步 I/O 会失去持久性,但您仍然保留 ACID 的其他部分:原子性、一致性、隔离性。许多应用程序在没有持久性的情况下也能正常运行。
异步 I/O 通过创建一个 SQLite VFS 对象 并使用 sqlite3_vfs_register() 注册它来工作。当通过此 VFS 打开的文件被写入(使用 vfs xWrite() 方法)时,数据不会直接写入磁盘,而是放置在“写入队列”中,由后台线程处理。
当从使用异步 VFS 打开的文件中读取数据时(使用 vfs xRead() 方法),数据从磁盘上的文件和写入队列中读取,因此从 vfs 读取器的角度来看,xWrite() 似乎已经完成。
异步 I/O VFS 通过调用 API 函数 sqlite3async_initialize() 和 sqlite3async_shutdown() 来注册(和注销)。有关详细信息,请参阅下面的“编译和使用”部分。
为了获得围绕异步 IO 的主要思想的经验,此实现有意保持简单。将来可能会添加其他功能。
例如,根据当前的实现,如果写入以稳定的流持续发生,并且超过了后台写入线程的 I/O 能力,则挂起的写入操作队列将无限增长。如果这种情况持续足够长的时间,主机系统可能会耗尽内存。更复杂的模块可以跟踪挂起写入的数量,并在挂起写入队列增长过大时停止接受新的写入请求。
来自单个进程的多个连接可以使用此异步 IO 实现并发访问单个数据库文件。从用户的角度来看,如果所有连接都来自单个进程,则“正常”SQLite 和使用异步后端的 SQLite 提供的并发性之间没有区别。
如果启用了文件锁定(默认情况下已启用),则来自多个进程的连接也可以读取和写入数据库文件。但是并发性会降低,如下所示
当使用异步 IO 的连接开始数据库事务时,数据库会立即被锁定。但是,直到写入队列中所有相关操作都刷新到磁盘后,才会释放锁。这意味着(例如)在发出“COMMIT”或“ROLLBACK”之后,数据库可能会保持锁定一段时间。
如果使用异步 IO 的应用程序连续快速执行事务,则其他数据库用户可能会被有效地锁定在数据库之外。这是因为当执行 BEGIN 时,会立即建立数据库锁。但是,当相应的 COMMIT 或 ROLLBACK 发生时,锁不会被释放,直到写入队列的相关部分被刷新。因此,如果在写入队列刷新之前,COMMIT 后跟着 BEGIN,则数据库永远不会解锁,从而阻止其他进程访问数据库。
可以使用 sqlite3async_control() API(见下文)在运行时禁用文件锁定。当使用 NFS 或其他网络文件系统时,这可能会提高性能,因为避免了建立文件锁所需的同步往返服务器。但是,如果多个连接尝试在禁用文件锁定时访问同一个数据库文件,则应用程序崩溃和数据库损坏可能是结果。
异步 IO 扩展包含一个 C 代码文件(sqlite3async.c)和一个头文件(sqlite3async.h),位于 SQLite 源代码树的 ext/async/ 子文件夹 中,该文件定义了应用程序用来激活和控制模块功能的 C API。
要使用异步 IO 扩展,请将 sqlite3async.c 编译为使用 SQLite 的应用程序的一部分。然后使用 sqlite3async.h 中定义的 API 来初始化和配置模块。
异步 IO VFS API 在 sqlite3async.h 中的注释中进行了详细描述。使用 API 通常包括以下步骤
通过调用 sqlite3async_initialize() 函数将异步 IO VFS 注册到 SQLite。
创建一个后台线程来执行写入操作并调用 sqlite3async_run()。
使用正常的 SQLite API 通过异步 IO VFS 读取和写入数据库。
有关详细信息,请参阅 sqlite3async.h 头文件 中的注释。
目前,异步 IO 扩展与 win32 系统以及支持 pthreads 接口的系统兼容,包括 Mac OS X、Linux 和其他各种 Unix。
要将异步 IO 扩展移植到另一个平台,用户必须为新平台实现互斥体和条件变量原语。目前没有可外部访问的接口允许这样做,但是修改 sqlite3async.c 中的代码以包含新平台的并发原语相对容易。在 sqlite3async.c 中搜索注释字符串“PORTING FUNCTIONS”以获取详细信息。然后实现以下每个函数的新版本
static void async_mutex_enter(int eMutex); static void async_mutex_leave(int eMutex); static void async_cond_wait(int eCond, int eMutex); static void async_cond_signal(int eCond); static void async_sched_yield(void);
每个函数所需的功能在 sqlite3async.c 中的注释中进行了描述。
此页面上次修改于 2023-10-10 17:29:48 UTC