从 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 的官方支持接口。

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 模块来实现定制的操作系统层。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 不是默认的,那么您可能必须使用新的sqlite3_open_v2() API 才能使用它。但是请注意,如果新的 VFS 是 SQLite 唯一已知的 VFS(如果 SQLite 编译时没有使用其通常的默认 VFS,或者使用sqlite3_vfs_unregister()删除了预编译的默认 VFS) 那么无论sqlite3_vfs_register()的 makeDflt 参数如何,新的 VFS 都会自动成为默认 VFS 。

标准构建包括默认的“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。

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()

对于 SQLite 版本 3.5.0, sqlite3_vfs 的 iVersion 字段应为 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 时所比较的名称。

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

sqlite3_vfs对象 的其余字段都存储指向实现原始操作的函数的指针。我们称这些为“方法”。第一种方法 xOpen 用于打开底层存储介质上的文件。结果是一个sqlite3_file 对象。还有一些额外的方法,由sqlite3_file 对象本身定义,用于读写和关闭文件。其他方法详述如下。文件名采用 UTF-8 格式。SQLite 将保证传递给 xOpen() 的 zFilename 字符串是由 xFullPathname() 生成的完整路径名,并且该字符串在调用 xClose() 之前有效且不变。所以sqlite3_file如果出于某种原因需要记住文件名,可以存储指向文件名的指针。xOpen() 的标志参数是 sqlite3_open_v2() 的标志参数的副本。如果使用了 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标志意味着文件在关闭时应该被删除。这将始终为 TEMP 数据库和期刊以及子期刊设置。SQLITE_OPEN_EXCLUSIVE 标志意味着文件应该以独占访问方式打开为除主数据库文件之外的所有文件设置此标志。作为第三个参数传递给 xOpen的sqlite3_file结构由调用者分配。xOpen 只是填充它。调用者为sqlite3_file 结构 分配最少的 szOsFile 字节。

SQLITE_OPEN_TEMP_DB数据库和 SQLITE_OPEN_TRANSIENT_DB数据库 之间的区别是:SQLITE_OPEN_TEMP_DB 用于显式声明和命名的 TEMP 表(使用 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 假定临时文件名是唯一的,而无需实际测试冲突,

xSleep 方法用于将调用线程挂起至少给定的微秒数。此方法用于实现sqlite3_sleep()sqlite3_busy_timeout() API。sqlite3_sleep()的情况下,始终使用默认 VFS 的 xSleep 方法。如果底层系统没有微秒分辨率的休眠能力,那么休眠时间应该向上取整。xSleep 返回这个向上舍入的值。

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

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_RESERVED类似于SQLITE_LOCK_SHARED因为它是读取文件的权限。但是在任何时间点,只有一个连接可以持有保留锁。SQLITE_LOCK_PENDING也是读取文件的权限其他连接也可以继续读取文件,但不允许其他连接将锁从无锁升级为共享锁。 SQLITE_LOCK_EXCLUSIVE是对文件的写入权限。只有一个连接可以持有独占锁,当一个连接持有独占锁时,没有其他连接可以持有任何锁(“无”除外)。xLock成功返回SQLITE_OK ,如果无法获得锁则返回 SQLITE_BUSY,或者SQLITE_IOERR_RDLOCK如果出现其他问题。xUnlock 方法在成功 时返回SQLITE_OK ,在出现问题时返回SQLITE_IOERR_UNLOCK

xCheckReservedLock() 方法检查另一个连接或另一个进程当前是否持有文件上的保留锁、挂起锁或独占锁。它返回真或假。

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

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

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

SQLITE_IOCAP_ATOMIC位表示对该设备的所有写入都是原子的,即整个写入发生或全部不发生。其他 SQLITE_IOCAP_ATOMIC nnn值表示指定大小的对齐块的写入是原子的。 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. 实现打开文件并填充 sqlite3_file对象的 xOpen 方法,包括将 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.0 版中发生了变化。

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

应用程序不需要使用这些标准内存分配器中的任何一个。如果使用SQLITE_OMIT_MEMORY_ALLOCATION编译 SQLite,则不 提供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_BUSY如果另一个线程已经存在。 sqlite3_mutex_leave()退出一个互斥体。互斥锁一直保持到出口数量与入口数量匹配为止。如果在线程当前未持有的互斥量上调用sqlite3_mutex_leave() ,则行为未定义。如果为释放的互斥量调用任何例程,则行为未定义。

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

嵌入式应用程序可能希望提供自己的互斥体实现。如果使用 -DSQLITE_MUTEX_APPDEF=1 编译时标志编译 SQLite,则 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 源代码合并用于嵌入式项目而无需更改,从而大大简化了配置管理。并且由此产生的系统对高度线程化设计的容忍度要高得多。