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

从 SQLite 3.4.2 迁移到 3.5.0

SQLite 3.5.0 版(2007-09-04)引入了一个新的操作系统接口层,与所有先前版本的 SQLite 不兼容。此外,一些现有的接口已被泛化,以便在进程内的所有数据库连接中工作,而不仅仅是在单个线程内的所有连接中工作。本文档旨在详细描述 3.5.0 的更改,以便先前版本 SQLite 的用户可以判断升级到较新版本需要付出多少努力(如果有的话)。

1.0 更改概述

此处提供了 SQLite 3.5.0 版本中更改的快速枚举。后续部分将更详细地描述这些更改。

  1. 操作系统接口层已完全重写
    1. 未公开的 sqlite3_os_switch() 接口已被移除。
    2. SQLITE_ENABLE_REDEF_IO 编译时标志不再起作用。I/O 程序现在始终可重新定义。
    3. 定义了三个新对象用于指定 I/O 程序:sqlite3_vfssqlite3_filesqlite3_io_methods
    4. 使用三个新接口创建替代操作系统接口:sqlite3_vfs_register()sqlite3_vfs_unregister()sqlite3_vfs_find()
    5. 添加了一个新接口,用于提供对创建新数据库连接的更多控制:sqlite3_open_v2()。旧版接口 sqlite3_open()sqlite3_open16() 继续得到完全支持。
  2. 3.3.0 版中引入的可选共享缓存和内存管理功能现在可以在同一进程内的多个线程中使用。以前,这些扩展仅适用于在单个线程中运行的数据库连接。
    1. sqlite3_enable_shared_cache() 接口现在适用于进程内的所有线程,而不仅仅是运行它的那个线程。
    2. sqlite3_soft_heap_limit() 接口现在适用于进程内的所有线程,而不仅仅是运行它的那个线程。
    3. sqlite3_release_memory() 接口现在将尝试减少所有线程中所有数据库连接的内存使用量,而不仅仅是调用该接口的线程中的连接。
    4. sqlite3_thread_cleanup() 接口已变为无操作。
  3. 已取消对多个线程使用相同数据库连接的限制。现在多个线程可以同时安全地使用相同的数据库连接。
  4. 现在有一个编译时选项,允许应用程序定义替代 malloc()/free() 实现,而无需修改任何核心 SQLite 代码。
  5. 现在有一个编译时选项,允许应用程序定义替代互斥锁实现,而无需修改任何核心 SQLite 代码。

在这些更改中,只有 1a 和 2a 到 2c 在任何正式意义上都是不兼容的。但是,以前对 SQLite 源代码进行过自定义修改的用户(例如,为嵌入式硬件添加自定义操作系统层),可能会发现这些更改具有更大的影响。另一方面,这些更改的一个重要目标是使自定义 SQLite 以在不同的操作系统上使用变得更加容易。

2.0 操作系统接口层

如果您的系统为 SQLite 定义了自定义操作系统接口,或者您正在使用未公开的 sqlite3_os_switch() 接口,则需要进行修改才能升级到 SQLite 3.5.0 版。乍一看这似乎很痛苦。但是,当您仔细查看时,您可能会发现新的 SQLite 接口使您的更改变得更小,更容易理解和管理。您的更改现在也可能与 SQLite 合并版本无缝协作。您将不再需要对 SQLite 源代码进行任何更改。所有更改都可以通过应用程序代码实现,并且您可以链接到 SQLite 合并版本的标准、未修改版本。此外,以前未公开的操作系统接口层现在是 SQLite 的正式支持接口。因此,您可以确信这将是一次性更改,并且您的新后端将继续在 SQLite 的未来版本中工作。

2.1 虚拟文件系统对象

SQLite 的新操作系统接口构建在一个名为 sqlite3_vfs 的对象周围。“vfs”代表“虚拟文件系统”。sqlite3_vfs 对象基本上是一个结构,其中包含指向函数的指针,这些函数实现 SQLite 需要执行的基本磁盘 I/O 操作,以便读取和写入数据库。在本文档中,我们通常将 sqlite3_vfs 对象称为“VFS”。

SQLite 能够同时使用多个 VFS。每个单独的数据库连接都与一个 VFS 相关联。但是,如果您有多个数据库连接,则每个连接可以与不同的 VFS 相关联。

始终存在一个默认的 VFS。旧版接口 sqlite3_open()sqlite3_open16() 始终使用默认的 VFS。创建数据库连接的新接口 sqlite3_open_v2() 允许您按名称指定要使用的 VFS。

2.1.1 注册新的 VFS 对象

Unix 或 Windows 的 SQLite 标准版本附带一个名为“unix”或“win32”的单个 VFS,具体取决于情况。此 VFS 也是默认 VFS。因此,如果您使用旧版打开函数,则一切将继续按以前的方式运行。更改在于应用程序现在可以灵活地添加新的 VFS 模块以实现自定义的操作系统层。sqlite3_vfs_register() API 可用于告诉 SQLite 一个或多个应用程序定义的 VFS 模块。

int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);

应用程序可以在任何时候调用 sqlite3_vfs_register(),当然,需要在使用 VFS 之前注册它。第一个参数是指向应用程序准备的自定义 VFS 对象的指针。第二个参数为 true,表示使新的 VFS 成为默认 VFS,以便旧版 sqlite3_open()sqlite3_open16() API 使用它。如果新的 VFS 不是默认 VFS,则您可能需要使用新的 sqlite3_open_v2() API 来使用它。但是请注意,如果新的 VFS 是 SQLite 已知的唯一 VFS(如果 SQLite 在没有其通常的默认 VFS 的情况下编译,或者如果使用 sqlite3_vfs_unregister() 移除预编译的默认 VFS),则新的 VFS 自动成为默认 VFS,而不管 sqlite3_vfs_register() 的 makeDflt 参数如何。

标准版本包括默认的“unix”或“win32”VFS。但是,如果您使用 -DOS_OTHER=1 编译时选项,则 SQLite 将在没有默认 VFS 的情况下构建。在这种情况下,应用程序必须在调用 sqlite3_open() 之前注册至少一个 VFS。这是嵌入式应用程序应该使用的方法。与其修改 SQLite 源代码以插入替代操作系统层(如 SQLite 的先前版本中所做的那样),不如使用 -DOS_OTHER=1 选项编译未修改的 SQLite 源文件(最好是合并版本),然后调用 sqlite3_vfs_register() 在创建任何数据库连接之前定义底层文件系统的接口。

2.1.2 对 VFS 对象的额外控制

sqlite3_vfs_unregister() API 用于从系统中移除现有的 VFS。

int sqlite3_vfs_unregister(sqlite3_vfs*);

sqlite3_vfs_find() API 用于按名称查找特定的 VFS。其原型如下所示

sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);

参数是所需 VFS 的符号名称。如果参数是 NULL 指针,则返回默认 VFS。该函数返回指向实现 VFS 的 sqlite3_vfs 对象的指针。或者,如果找不到与搜索条件匹配的对象,则返回 NULL 指针。

2.1.3 现有 VFS 的修改

注册 VFS 后,不应再对其进行修改。如果需要更改行为,则应注册新的 VFS。应用程序可能可以使用 sqlite3_vfs_find() 找到旧的 VFS,将旧的 VFS 复制到新的 sqlite3_vfs 对象中,对新的 VFS 进行所需的修改,注销旧的 VFS,然后将其替换为新的 VFS。即使在注销旧的 VFS 后,现有的数据库连接也会继续使用旧的 VFS,但新的数据库连接将使用新的 VFS。

2.1.4 VFS 对象

VFS 对象是以下结构的实例

typedef struct sqlite3_vfs sqlite3_vfs;
struct sqlite3_vfs {
  int iVersion;            /* Structure version number */
  int szOsFile;            /* Size of subclassed sqlite3_file */
  int mxPathname;          /* Maximum file pathname length */
  sqlite3_vfs *pNext;      /* Next registered VFS */
  const char *zName;       /* Name of this virtual file system */
  void *pAppData;          /* Pointer to application-specific data */
  int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
               int flags, int *pOutFlags);
  int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
  int (*xAccess)(sqlite3_vfs*, const char *zName, int flags);
  int (*xGetTempName)(sqlite3_vfs*, char *zOut);
  int (*xFullPathname)(sqlite3_vfs*, const char *zName, char *zOut);
  void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
  void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
  void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol);
  void (*xDlClose)(sqlite3_vfs*, void*);
  int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
  int (*xSleep)(sqlite3_vfs*, int microseconds);
  int (*xCurrentTime)(sqlite3_vfs*, double*);
  /* New fields may be appended in figure versions.  The iVersion
  ** value will increment whenever this happens. */
};

要创建新的 VFS,应用程序需要使用适当的值填充此结构的实例,然后调用 sqlite3_vfs_register()

sqlite3_vfs 的 iVersion 字段对于 SQLite 3.5.0 版应为 1。如果我们必须以某种方式修改 VFS 对象,则此数字可能会在 SQLite 的未来版本中增加。我们希望这种情况永远不会发生,但如果发生这种情况,则会做出规定。

szOsFile 字段是定义打开文件的结构(sqlite3_file 对象)的大小(以字节为单位)。稍后将更全面地描述此对象。这里的要点是,每个 VFS 实现都可以定义自己的 sqlite3_file 对象,其中包含 VFS 实现需要存储的有关打开文件的任何信息。但是,SQLite 需要知道此对象的大小,以便预分配足够的空间来容纳它。

mxPathname 字段是此 VFS 可以使用的文件路径名的最大长度。SQLite 有时必须预分配此大小的缓冲区,因此它应尽可能小。某些文件系统允许使用巨大的路径名,但在实践中,路径名很少超过 100 个字节左右。您不必在此处放置底层文件系统可以处理的最长路径名。您只需放置您希望 SQLite 能够处理的最长路径名即可。在大多数情况下,几百个是一个不错的值。

pNext 字段由 SQLite 在内部使用。具体来说,SQLite 使用此字段来形成已注册 VFS 的链接列表。

zName 字段是 VFS 的符号名称。这是 sqlite3_vfs_find() 在查找 VFS 时进行比较的名称。

pAppData 指针未由 SQLite 核心使用。该指针可用于存储 VFS 信息可能想要携带的辅助信息。

sqlite3_vfs 对象的其余字段都存储指向实现基本操作的函数的指针。我们称这些为“方法”。第一个方法 xOpen 用于在底层存储介质上打开文件。结果是一个 sqlite3_file 对象。由 sqlite3_file 对象本身定义的其他方法用于读取、写入和关闭文件。下面详细介绍了其他方法。文件名采用 UTF-8 编码。SQLite 将保证传递给 xOpen() 的 zFilename 字符串是通过 xFullPathname() 生成的完整路径名,并且该字符串在调用 xClose() 之前将保持有效且不变。因此,如果 sqlite3_file 需要出于某种原因记住文件名,则可以存储指向该文件名的指针。xOpen() 的 flags 参数是 sqlite3_open_v2() 的 flags 参数的副本。如果使用 sqlite3_open() 或 sqlite3_open16(),则 flags 为 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE。如果 xOpen() 以只读方式打开文件,则它将设置 *pOutFlags 以包含 SQLITE_OPEN_READONLY。*pOutFlags 中的其他位可能会被设置。根据被打开的对象,SQLite 还会将以下标志之一添加到 xOpen() 调用中

文件 I/O 实现可以使用对象类型标志来更改处理文件的方式。例如,一个不关心崩溃恢复或回滚的应用程序可能会使日志文件打开成为一个无操作。写入此日志也是一个无操作。任何尝试读取日志都会返回 SQLITE_IOERR。或者,实现可能会识别数据库文件将以随机顺序进行页面对齐的扇区读取和写入,并相应地设置其 I/O 子系统。SQLite 还可以将以下标志之一添加到 xOpen 方法中SQLITE_OPEN_DELETEONCLOSE 标志表示在关闭文件时应将其删除。对于临时数据库和日志文件以及子日志文件,始终设置此标志。 SQLITE_OPEN_EXCLUSIVE 标志表示应以独占方式打开文件。除主数据库文件外,此标志对所有文件都设置。传递给 xOpen 的第三个参数 sqlite3_file 结构由调用方分配。xOpen 只填充它。调用方为 sqlite3_file 结构分配至少 szOsFile 字节的空间。

SQLITE_OPEN_TEMP_DB 数据库和 SQLITE_OPEN_TRANSIENT_DB 数据库之间的区别在于: SQLITE_OPEN_TEMP_DB 用于显式声明和命名的临时表(使用 CREATE TEMP TABLE 语法)或用于通过打开文件名为空字符串的数据库创建的临时数据库中的命名表。 SQLITE_OPEN_TRANSIENT_DB 保存 SQLite 自动创建的数据库表,以评估子查询或 ORDER BY 或 GROUP BY 子句。TEMP_DB 和 TRANSIENT_DB 数据库都是私有的,并且会自动删除。TEMP_DB 数据库在数据库连接期间持续存在。TRANSIENT_DB 数据库仅在单个 SQL 语句期间持续存在。

xDelete 方法用于删除文件。文件的名称在第二个参数中给出。文件名将采用 UTF-8 编码。VFS 必须将文件名转换为底层操作系统期望的任何字符表示形式。如果 syncDir 参数为真,则 xDelete 方法应在将包含已删除文件的目录的目录内容的更改同步到磁盘之前不返回,以确保如果断电很快发生,文件不会“重新出现”。

xAccess 方法用于检查文件的访问权限。文件名将采用 UTF-8 编码。flags 参数将为 SQLITE_ACCESS_EXISTS 以检查文件是否存在, SQLITE_ACCESS_READWRITE 以检查文件是否可读写,或 SQLITE_ACCESS_READ 以检查文件是否至少可读。“文件”由第二个参数命名,可能是目录或文件夹名称。

xGetTempName 方法计算 SQLite 可以使用的临时文件的文件名。名称应写入第二个参数给出的缓冲区中。SQLite 将调整该缓冲区的大小以容纳至少 mxPathname 字节。生成的文件名应采用 UTF-8 编码。为了避免安全问题,生成的文件名应包含足够的随机性,以防止攻击者提前猜测临时文件名。

xFullPathname 方法用于将相对路径名转换为完整路径名。生成的完整路径名将写入第三个参数提供的缓冲区中。SQLite 将将输出缓冲区的大小调整为至少 mxPathname 字节。输入和输出名称都应采用 UTF-8 编码。

xDlOpen、xDlError、xDlSym 和 xDlClose 方法都用于在运行时访问共享库。如果库使用 SQLITE_OMIT_LOAD_EXTENSION 编译或如果从未使用 sqlite3_enable_load_extension() 接口启用动态扩展加载,则可以省略这些方法(并将它们的指针设置为零)。xDlOpen 方法打开共享库或 DLL 并返回指向句柄的指针。如果打开失败,则返回 NULL。如果打开失败,则可以使用 xDlError 方法获取文本错误消息。该消息将写入第三个参数的 zErrMsg 缓冲区中,该缓冲区的长度至少为 nByte 字节。xDlSym 返回指向共享库中符号的指针。符号的名称由第二个参数给出。假定使用 UTF-8 编码。如果找不到符号,则返回 NULL 指针。xDlClose 例程关闭共享库。

xRandomness 方法恰好使用一次来初始化 SQLite 内部伪随机数生成器 (PRNG)。仅使用默认 VFS 上的 xRandomness 方法。SQLite 永远不会访问其他 VFS 上的 xRandomness 方法。xRandomness 例程请求将 nByte 字节的随机性写入 zOut。例程返回获得的随机性字节的实际数量。如此获得的随机性的质量将决定内置 SQLite 函数(如 random() 和 randomblob())生成的随机性的质量。SQLite 还使用其 PRNG 生成临时文件名。在某些平台(例如:Windows)上,SQLite 假设临时文件名是唯一的,而无需实际测试冲突,因此即使从未使用 random() 和 randomblob() 函数,拥有高质量的随机性也很重要。

xSleep 方法用于挂起调用线程至少给定微秒数。此方法用于实现 sqlite3_sleep()sqlite3_busy_timeout() API。在 sqlite3_sleep() 的情况下,始终使用默认 VFS 的 xSleep 方法。如果底层系统没有微秒级分辨率的休眠功能,则应将休眠时间四舍五入。xSleep 返回此四舍五入的值。

xCurrentTime 方法查找当前时间和日期,并将结果作为双精度浮点值写入第二个参数提供的指针中。时间和日期采用协调世界时 (UTC),是一个分数儒略日数。

2.1.5 打开的文件对象

打开文件的结果是 sqlite3_file 对象的实例。 sqlite3_file 对象是一个抽象基类,定义如下

typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
  const struct sqlite3_io_methods *pMethods;
};

每个 VFS 实现都将通过在末尾添加其他字段来对 sqlite3_file 进行子类化,以保存 VFS 需要了解的有关打开文件的任何信息。存储什么信息无关紧要,只要结构的总大小不超过 sqlite3_vfs 对象中记录的 szOsFile 值即可。

sqlite3_io_methods 对象是一个结构,其中包含指向用于读取、写入和以其他方式处理文件的函数的指针。此对象定义如下

typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
  int iVersion;
  int (*xClose)(sqlite3_file*);
  int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
  int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
  int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
  int (*xSync)(sqlite3_file*, int flags);
  int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
  int (*xLock)(sqlite3_file*, int);
  int (*xUnlock)(sqlite3_file*, int);
  int (*xCheckReservedLock)(sqlite3_file*);
  int (*xFileControl)(sqlite3_file*, int op, void *pArg);
  int (*xSectorSize)(sqlite3_file*);
  int (*xDeviceCharacteristics)(sqlite3_file*);
  /* Additional methods may be added in future releases */
};

sqlite3_io_methods 的 iVersion 字段作为未来增强功能的保险。对于 SQLite 3.5 版本,iVersion 值应始终为 1。

xClose 方法关闭文件。 sqlite3_file 结构的空间由调用方释放。但是,如果 sqlite3_file 包含指向其他已分配内存或资源的指针,则 xClose 方法应释放这些分配。

xRead 方法从文件开头偏移 iOfst 字节处读取 iAmt 字节。读取的数据存储在第二个参数的指针中。xRead 在成功时返回 SQLITE_OK,如果由于到达文件末尾而无法读取完整字节数,则返回 SQLITE_IOERR_SHORT_READ,或者对于任何其他错误返回 SQLITE_IOERR_READ

xWrite 方法将第二个参数中的 iAmt 字节数据写入文件,从偏移 iOfst 字节处开始。如果在写入之前文件的大小小于 iOfst 字节,则 xWrite 应确保文件扩展到 iOfst 字节之前,然后开始写入。xWrite 继续根据需要扩展文件,以便在 xWrite 调用结束时文件的大小至少为 iAmt+iOfst 字节。xWrite 方法在成功时返回 SQLITE_OK。如果写入无法完成,因为底层存储介质已满,则返回 SQLITE_FULL。对于任何其他错误,应返回 SQLITE_IOERR_WRITE

xTruncate 方法将文件截断为 nByte 字节长度。如果文件已经具有 nByte 字节或更短的长度,则此方法是一个无操作。xTruncate 方法在成功时返回 SQLITE_OK,如果出现任何错误则返回 SQLITE_IOERR_TRUNCATE

xSync 方法用于强制先前写入的数据从操作系统缓存中移出并进入非易失性内存。第二个参数通常为 SQLITE_SYNC_NORMAL。如果第二个参数为 SQLITE_SYNC_FULL,则 xSync 方法应确保数据也已从磁盘控制器的缓存中刷新。 SQLITE_SYNC_FULL 参数等效于 Mac OS X 上的 F_FULLSYNC ioctl()。xSync 方法在成功时返回 SQLITE_OK,如果出现任何错误则返回 SQLITE_IOERR_FSYNC

xFileSize() 方法确定文件以字节为单位的当前大小,并将该值写入 *pSize。它在成功时返回 SQLITE_OK,如果出现问题则返回 SQLITE_IOERR_FSTAT

xLock 和 xUnlock 方法用于设置和清除文件锁。SQLite 支持五个级别的文件锁,按顺序排列

底层实现可以支持这些锁定级别的某个子集,只要它满足本段的其他要求即可。锁定级别作为 xLock 和 xUnlock 的第二个参数指定。xLock 方法将锁定级别提高到指定的锁定级别或更高。xUnlock 方法将锁定级别降低到不低于指定的级别。 SQLITE_LOCK_NONE 表示文件已解锁。 SQLITE_LOCK_SHARED 允许读取文件。多个数据库连接可以同时持有 SQLITE_LOCK_SHAREDSQLITE_LOCK_RESERVEDSQLITE_LOCK_SHARED 类似,它允许读取文件。但任何时候只能有一个连接持有保留锁。 SQLITE_LOCK_PENDING 也允许读取文件。其他连接也可以继续读取文件,但任何其他连接都不允许将锁从 none 升级到 shared。 SQLITE_LOCK_EXCLUSIVE 允许写入文件。只有一个连接可以持有排他锁,并且当一个连接持有排他锁时,任何其他连接都不能持有任何锁(除了“none”)。xLock 成功时返回 SQLITE_OK,如果无法获得锁则返回 SQLITE_BUSY,如果出现其他错误则返回 SQLITE_IOERR_RDLOCK。xUnlock 方法成功时返回 SQLITE_OK,出现问题时返回 SQLITE_IOERR_UNLOCK

xCheckReservedLock() 方法检查另一个连接或另一个进程是否当前正在持有文件的保留、挂起或排他锁。它返回 true 或 false。

xFileControl() 方法是一个通用接口,允许自定义 VFS 实现使用(新的和实验性的) sqlite3_file_control() 接口直接控制打开的文件。第二个“op”参数是整数操作码。第三个参数是一个通用指针,它旨在指向一个结构体,该结构体可能包含参数或用于写入返回值的空间。xFileControl() 的潜在用途可能是启用带有超时的阻塞锁、更改锁定策略(例如使用点文件锁)、查询锁的状态或破坏陈旧锁的功能。SQLite 核心保留小于 100 的操作码供自身使用。 操作码列表 小于 100。定义自定义 xFileControl 方法的应用程序应使用大于 100 的操作码以避免冲突。

xSectorSize 返回底层非易失性介质的“扇区大小”。“扇区”被定义为可以写入而不会干扰相邻存储的最小存储单元。在磁盘驱动器上,“扇区大小”直到最近一直是 512 字节,尽管存在将其值增加到 4KiB 的趋势。SQLite 需要知道扇区大小,以便它可以一次写入一个完整的扇区,从而避免在写入过程中发生电源故障时损坏相邻的存储空间。

xDeviceCharacteristics 方法返回一个整数位向量,该向量定义了底层存储介质可能具有的任何特殊属性,SQLite 可以使用这些属性来提高性能。允许的返回值是以下值的按位或

SQLITE_IOCAP_ATOMIC 位表示对该设备的所有写入都是原子的,这意味着要么整个写入发生,要么都不发生。其他 SQLITE_IOCAP_ATOMICnnn 值表示对指示大小的对齐块的写入是原子的。 SQLITE_IOCAP_SAFE_APPEND 表示当使用新数据扩展文件时,首先写入新数据,然后更新文件大小。因此,如果发生电源故障,则不会有任何机会导致文件可能被随机扩展。 SQLITE_IOCAP_SEQUENTIAL 位表示所有写入都按发出顺序发生,并且不会由底层文件系统重新排序。

2.1.6 构造新 VFS 的检查清单

前面的段落包含大量信息。为了简化构建 SQLite 新 VFS 的任务,我们提供以下实现检查清单

  1. 定义 sqlite3_file 对象的适当子类。
  2. 实现 sqlite3_io_methods 对象所需的方法。
  3. 创建一个包含指向上一步骤中方法的指针的静态且常量的 sqlite3_io_methods 对象。
  4. 实现 xOpen 方法,该方法打开文件并填充 sqlite3_file 对象,包括设置 pMethods 指向上一步骤中的 sqlite3_io_methods 对象。
  5. 实现 sqlite3_vfs 所需的其他方法。
  6. 定义一个静态(但不是常量)的 sqlite3_vfs 结构,其中包含指向 xOpen 方法和其他方法的指针,并且包含 iVersion、szOsFile、mxPathname、zName 和 pAppData 的适当值。
  7. 实现一个调用 sqlite3_vfs_register() 并将指向上一步骤中的 sqlite3_vfs 结构的指针传递给它的过程。此过程可能是实现 VFS 的源文件中唯一导出的符号。

在您的应用程序中,在打开任何数据库连接之前,作为初始化过程的一部分,调用上一步骤中实现的过程。

3.0 内存分配子系统

从 3.5 版开始,SQLite 使用例程 sqlite3_malloc()sqlite3_free()sqlite3_realloc() 获取其所需的所有堆内存。这些例程在 SQLite 的早期版本中就已存在,但 SQLite 之前绕过了这些例程并使用了它自己的内存分配器。所有这些都将在 3.5 版中发生变化。

SQLite 源代码树实际上包含多个版本的内存分配器。在“mem1.c”源文件中找到的默认高速版本用于大多数构建。但是,如果启用了 SQLITE_MEMDEBUG 标志,则会改为使用“mem2.c”源文件中的单独内存分配器。mem2.c 分配器实现了大量挂钩来执行错误检查和模拟内存分配失败以进行测试。这两个分配器都使用标准 C 库中的 malloc()/free() 实现。

应用程序不需要使用这两个标准内存分配器中的任何一个。如果 SQLite 使用 SQLITE_OMIT_MEMORY_ALLOCATION 编译,则不提供 sqlite3_malloc()sqlite3_realloc()sqlite3_free() 函数的实现。相反,链接到 SQLite 的应用程序必须提供这些函数的自己的实现。应用程序提供的内存分配器不需要使用标准 C 库中的 malloc()/free() 实现。例如,嵌入式应用程序可能会提供一个替代内存分配器,该分配器使用固定内存池的内存,该内存池专门用于 SQLite。

实现自己的内存分配器的应用程序必须为通常的三个分配函数 sqlite3_malloc()sqlite3_realloc()sqlite3_free() 提供实现。并且他们还必须实现第四个函数

int sqlite3_memory_alarm(
  void(*xCallback)(void *pArg, sqlite3_int64 used, int N),
  void *pArg,
  sqlite3_int64 iThreshold
);

sqlite3_memory_alarm 例程用于在内存分配事件上注册回调。此例程注册或清除一个回调,该回调在分配的内存量超过 iThreshold 时触发。一次只能注册一个回调。每次调用 sqlite3_memory_alarm() 都会覆盖之前的回调。通过将 xCallback 设置为 NULL 指针来禁用回调。

回调的参数是 pArg 值、当前使用的内存量以及引发回调的分配的大小。回调可能会调用 sqlite3_free() 来释放内存空间。回调可能会调用 sqlite3_malloc()sqlite3_realloc(),但如果这样做,递归调用将不会调用任何其他回调。

sqlite3_soft_heap_limit() 接口通过在软堆限制处注册内存警报并在警报回调中调用 sqlite3_release_memory() 来工作。应用程序程序不应尝试使用 sqlite3_memory_alarm() 接口,因为这样做会干扰 sqlite3_soft_heap_limit() 模块。仅公开此接口是为了使应用程序能够在使用 SQLITE_OMIT_MEMORY_ALLOCATION 编译 SQLite 核心时提供自己的替代实现。

SQLite 中的内置内存分配器还提供以下其他接口

sqlite3_int64 sqlite3_memory_used(void);
sqlite3_int64 sqlite3_memory_highwater(int resetFlag);

应用程序可以使用这些接口来监视 SQLite 使用了多少内存。 sqlite3_memory_used() 例程返回当前使用的内存字节数, sqlite3_memory_highwater() 返回最大瞬时内存使用量。这两个例程都不包括与内存分配器相关的开销。这些例程供应用程序使用。SQLite 从不自行调用它们。因此,如果应用程序提供自己的内存分配子系统,则可以根据需要省略这些接口。

4.0 互斥子系统

SQLite 一直以来都是线程安全的,因为它可以安全地在不同的线程中同时使用不同的 SQLite 数据库连接。约束条件是同一个数据库连接不能在两个单独的线程中同时使用。SQLite 3.5.0 版放宽了此约束。

为了允许多个线程同时使用同一个数据库连接,SQLite 必须广泛使用互斥量。因此,添加了一个新的互斥量子系统。互斥量子系统具有以下接口

sqlite3_mutex *sqlite3_mutex_alloc(int);
void sqlite3_mutex_free(sqlite3_mutex*);
void sqlite3_mutex_enter(sqlite3_mutex*);
int sqlite3_mutex_try(sqlite3_mutex*);
void sqlite3_mutex_leave(sqlite3_mutex*);

尽管这些例程存在供 SQLite 核心使用,但如果需要,应用程序代码也可以自由使用这些例程。互斥量是 sqlite3_mutex 对象。 sqlite3_mutex_alloc() 例程分配一个新的互斥量对象并返回指向它的指针。 sqlite3_mutex_alloc() 的参数应为 SQLITE_MUTEX_FASTSQLITE_MUTEX_RECURSIVE,分别用于非递归和递归互斥量。如果底层系统不提供非递归互斥量,则在这种情况下可以使用递归互斥量代替。 sqlite3_mutex_alloc() 的参数也可以是指定几个静态互斥量之一的常量

这些静态互斥量保留供 SQLite 内部使用,应用程序不应使用它们。静态互斥量都是非递归的。

sqlite3_mutex_free() 例程应用于释放非静态互斥量。如果将静态互斥量传递给此例程,则行为未定义。

函数 sqlite3_mutex_enter() 尝试进入互斥锁,如果其他线程已进入则会阻塞。 sqlite3_mutex_try() 尝试进入互斥锁,成功则返回 SQLITE_OK,如果其他线程已进入则返回 SQLITE_BUSYsqlite3_mutex_leave() 退出互斥锁。互斥锁保持锁定状态,直到退出次数与进入次数相等。如果在当前线程未持有互斥锁的情况下调用 sqlite3_mutex_leave(),则行为未定义。如果对已释放的互斥锁调用任何例程,则行为未定义。

SQLite 源代码提供了这些 API 的多种实现,适用于不同的环境。如果 SQLite 使用 SQLITE_THREADSAFE=0 标记编译,则会提供一个无操作的互斥锁实现,该实现速度很快,但不会执行真正的互斥。该实现适用于单线程应用程序或仅在单线程中使用 SQLite 的应用程序。其他真正的互斥锁实现基于底层操作系统提供。

嵌入式应用程序可能希望提供自己的互斥锁实现。如果 SQLite 使用 -DSQLITE_MUTEX_APPDEF=1 编译时标记进行编译,则 SQLite 核心不提供任何互斥锁子系统,并且必须由链接到 SQLite 的应用程序提供符合上述接口的互斥锁子系统。

5.0 其他接口变更

SQLite 3.5.0 版本在某些 API 的行为上进行了技术上不兼容的更改。但是,这些 API 很少使用,即使使用它们,也很难想象更改可能会破坏某些内容的情况。这些更改实际上使这些接口更加有用和强大。

在 3.5.0 版本之前,sqlite3_enable_shared_cache() API 将为单个线程中的所有连接启用和禁用共享缓存功能 - 与调用 sqlite3_enable_shared_cache() 例程相同的线程。使用共享缓存的数据库连接仅限于在其打开的线程中运行。从 3.5.0 版本开始,sqlite3_enable_shared_cache() 应用于进程中所有线程中的所有数据库连接。现在,在单独线程中运行的数据库连接可以共享缓存。并且使用共享缓存的数据库连接可以从一个线程迁移到另一个线程。

在 3.5.0 版本之前,sqlite3_soft_heap_limit() 为单个线程中的所有数据库连接设置堆内存使用的上限。每个线程都可以有自己的堆限制。从 3.5.0 版本开始,整个进程只有一个堆限制。这看起来更严格(一个限制而不是多个),但在实践中,这是大多数用户想要的。

在 3.5.0 版本之前,sqlite3_release_memory() 函数将尝试从与 sqlite3_release_memory() 调用相同的线程中的所有数据库连接中回收内存。从 3.5.0 版本开始,sqlite3_release_memory() 函数将尝试从所有线程中的所有数据库连接中回收内存。

6.0 总结

从 SQLite 3.4.2 版本过渡到 3.5.0 版本是一个重大变化。SQLite 核心中的每个源代码文件都必须进行修改,有些修改非常广泛。并且更改在 C 接口中引入了一些细微的不兼容性。但我们认为从 3.4.2 到 3.5.0 的过渡带来的好处远大于移植带来的痛苦。新的 VFS 层现在定义明确且稳定,应该会简化将来的自定义。VFS 层以及可分离的内存分配器和互斥锁子系统允许在嵌入式项目中无更改地使用标准的 SQLite 源代码合并,从而大大简化了配置管理。并且生成的系统对高度线程化的设计更加容忍。

此页面上次修改于 2022-01-08 05:02:57 UTC