从 SQLite 3.4.2 迁移到 3.5.0(二十)

返回:SQLite---系列文章目录

上一篇:SQLite---调试提示(十九)

下一篇:SQLite---系列文章目录

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 源(例如,为嵌入式添加自定义操作系统层 hardware) 可能会发现这些更改具有更大的影响。 另一方面,这些变化的一个重要目标是使 自定义 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对象基本上是一个包含指针的结构 到实现原始磁盘 I/O 操作的函数 SQLite需要执行才能读取和写入数据库。 在本文中,我们经常将sqlite3_vfs对象称为"VFS"。

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

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

2.1.1 注册新的 VFS 对象

适用于 Unix 或 Windows 的 SQLite 标准版本带有一个 VFS 根据需要命名为"unix"或"win32"。这个VFS也是 默认值。因此,如果您使用的是传统的开放函数,那么一切都会如此 将继续像以前一样运作。更改是应用程序 现在可以灵活地添加新的 VFS 模块来实现 自定义操作系统层。可以使用 sqlite3_vfs_register() API 要告诉 SQLite 一个或多个应用程序定义的 VFS 模块:

cpp 复制代码
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 或是否删除了预编译的默认 VFS 使用 sqlite3_vfs_unregister()) 则新的 VFS 自动变为 默认 VFS,无论 makeDflt 参数为 sqlite3_vfs_register()。

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

2.1.2 对 VFS 对象的附加控制

sqlite3_vfs_unregister() API 用于删除现有的 来自系统的 VFS。

cpp 复制代码
int sqlite3_vfs_unregister(sqlite3_vfs*);

sqlite3_vfs_find() API 用于定位特定的 VFS 按名称。其原型如下:

cpp 复制代码
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);

该参数是所需 VFS 的符号名称。如果 参数为 NULL 指针,则返回默认 VFS。 该函数返回指向 sqlite3_vfs 对象的指针,该对象 实现 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 对象是以下结构的实例:

cpp 复制代码
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。 如果有必要,这个数字可能会在 SQLite 的未来版本中增加 以某种方式修改 VFS 对象。我们希望这永远不会发生, 但该规定是在万一的情况下做出的。

szOsFile 字段是定义 打开的文件:sqlite3_file对象。将描述此对象 更全面地见下文。这里的重点是,每个 VFS 实现都可以 定义自己的 sqlite3_file 对象,其中包含任何信息 VFS 实现需要存储一个打开的文件。SQLite需求 但是,要知道这个对象有多大,以便预先分配足够的 空间来容纳它。

mxPathname 字段是文件路径名的最大长度,它 此 VFS 可以使用。SQLite 有时必须预先分配 这个尺寸,所以它应该尽可能小。一些 文件系统允许巨大的路径名,但实际上路径名很少 扩展超过 100 个字节左右。你不必把最长的 底层文件系统可以在此处处理的路径名。只有你 必须放置您希望SQLite能够的最长路径名 处理。在大多数情况下,几百个是物有所值的。

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

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

SQLite 核心未使用 pAppData 指针。指针是 可用于存储 VFS 信息可能提供的辅助信息 想随身携带。

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

文件 I/O 实现可以使用对象类型标志来执行以下操作 更改了处理文件的方式。例如,应用程序 不关心崩溃恢复或回滚,可能会使 打开日志文件 a no-op。对本期刊的写入是 也是一个不操作。任何读取日志的尝试都会返回SQLITE_IOERR。 或者,实现可能会识别数据库文件将 以随机顺序进行页面对齐的扇区读取和写入 并相应地设置其 I/O 子系统。 SQLite 还可以向 xOpen 添加以下标志之一 方法:

SQLITE_OPEN_DELETEONCLOSE 标志表示文件应为 关闭时删除。这将始终设置为 TEMP 数据库和期刊以及子期刊。SQLITE_OPEN_EXCLUSIVE 标志表示应打开文件 用于独家访问。此标志针对除 用于主数据库文件。 作为第三个参数传递的sqlite3_file结构 xOpen 由调用方分配。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数据库 last 仅在单个 SQL 语句的持续时间内。

xDelete 方法用于删除文件。文件的名称是 在第二个参数中给出。文件名将为 UTF-8。 VFS 必须将文件名转换为任何字符表示形式 底层操作系统期望。如果 syncDir 参数为 true,则 xDelete 方法在更改之前不应返回 添加到包含 已删除的文件已同步到磁盘,以确保 如果不久后发生电源故障,文件不会"重新出现"。

xAccess 方法用于检查文件的访问权限。 文件名将采用 UTF-8 编码。将SQLITE_ACCESS_EXISTS flags 参数来检查文件是否存在,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 方法。xRandomness SQLite 永远不会访问其他 VFS 上的方法。 xRandomness 例程请求 nByte 字节的随机性 写入 zOut。例程返回 获得的随机字节数。这样获得的随机性的质量 将决定内置生成的随机性的质量 SQLite 函数,例如 random() 和 randomblob()。SQLite 也 使用其 PRNG 生成临时文件名。在某些平台上 (例如:Windows)SQLite 假定临时文件名是唯一的 没有实际测试碰撞,所以有 即使 random() 和 randomblob() 从不使用函数。

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

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

2.1.5 Open File 对象

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

cpp 复制代码
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对象是包含指针的结构 用于读取、写入和以其他方式处理文件的方法。 此对象定义如下:

cpp 复制代码
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 字段作为保险提供 针对未来的增强功能。iVersion 值应始终为 1 表示 SQLite 版本 3.5。

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

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

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

xTruncate 方法将文件截断为长度为 nByte 字节。 如果文件的长度已经是 nByte 字节或更少,那么这个 方法是无操作的。xTruncate 方法返回 SQLITE_OK on 成功,如果出现任何问题,SQLITE_IOERR_TRUNCATE

xSync 方法用于强制将以前写入的数据从 操作系统缓存并进入非易失性存储器。第二个 参数通常为 SQLITE_SYNC_NORMAL。如果第二个参数 SQLITE_SYNC_FULL则 xSync 方法应确保 数据也已通过磁盘控制器缓存刷新。 SQLITE_SYNC_FULL 参数等效于 F_FULLSYNC ioctl() 在 Mac OS X 上。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() 方法检查另一个连接或 另一个进程当前持有保留、待处理或独占 锁定文件。它返回 true 或 false。

xFileControl() 方法是一个通用接口,允许自定义 VFS 实现,使用 (新的和实验性的)sqlite3_file_control() 接口。第二个"op"参数 是整数操作码。第三个 argument 是一个泛型指针,旨在成为指针 到可能包含参数或空间的结构,其中 写入返回值。xFileControl() 的潜在用途可能是 函数以启用超时阻塞锁,以更改 锁定策略(例如使用点文件锁),查询 关于锁的状态,或打破陈旧的锁。SQLite的 Core 保留的操作码少于 100 个供自己使用。 小于 100 的操作码列表可用。 定义自定义 xFileControl 方法的应用程序应使用操作码 大于 100 以避免冲突。

xSectorSize 返回基础的"扇区大小" 非易失性介质。"扇区"被定义为 可以在不干扰相邻存储的情况下写入的存储。 在磁盘驱动器上,"扇区大小"直到最近才为 512 字节, 尽管有人推动将该值增加到 4KiB。SQLite需求 知道扇区大小,以便它可以在 时间,从而避免损坏相邻的存储空间,如果电源 丢失发生在写入过程中。

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

SQLITE_IOCAP_ATOMIC 位表示对此设备的所有写入都是 原子,从某种意义上说,要么整个写入发生,要么不发生 发生。其他 SQLITE_IOCAP_ATOMICnnn 值表示 对指定大小的对齐块的写入是原子的。SQLITE_IOCAP_SAFE_APPEND 意味着在使用 new 扩展文件时 data,则先写入新数据,然后更新文件大小。 因此,如果发生电源故障,则文件不可能有 以随机性扩展。SQLITE_IOCAP_SEQUENTIAL位表示 所有写入都按照发出的顺序发生,而不是发出的顺序 由底层文件系统重新排序。

2.1.6 构建新 VFS 的清单

前面的段落包含了很多信息。 简化构建任务 一个新的 VFS for SQLite 我们提供以下实现清单:

  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 分配器实现了许多钩子 执行错误检查并模拟内存分配失败以进行测试 目的。这两个分配器都使用 malloc()/free() 实现 在标准 C 库中。

应用程序不需要使用这些标准内存中的任何一个 分配器。如果 SQLite 是用 SQLITE_OMIT_MEMORY_ALLOCATION 编译的,那么 sqlite3_malloc()sqlite3_realloc() 没有实现, 并提供 sqlite3_free() 函数。相反,应用程序 针对 SQLite 的链接必须提供自己的实现 功能。不需要应用程序提供的内存分配器 在标准 C 库中使用 malloc()/free() 实现。 嵌入式应用程序可能会提供备用内存分配器 将内存用于为独占预留的固定内存池 例如,使用 SQLite。

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

cpp 复制代码
int sqlite3_memory_alarm(
  void(*xCallback)(void *pArg, sqlite3_int64 used, int N),
  void *pArg,
  sqlite3_int64 iThreshold
);

sqlite3_memory_alarm例程用于注册 内存分配事件的回调。 此例程注册或清除在以下情况下触发的回调 分配的内存量超过 iThreshold。只 一次可以注册一个回调。每次通话 to 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 核心是 用SQLITE_OMIT_MEMORY_ALLOCATION编译。

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

cpp 复制代码
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必须广泛使用互斥锁。和 因此,添加了一个新的互斥子系统。互斥子系统 作为以下接口:

cpp 复制代码
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源代码合并 无需更改即可在嵌入式项目中使用,大大简化 配置管理。由此产生的系统要多得多 耐受高螺纹设计。

相关推荐
小柯J桑_2 分钟前
C++:探索AVL树旋转的奥秘
开发语言·c++·avl树
数学人学c语言2 分钟前
从熟练Python到入门学习C++(record 6)
c++·python·学习
深情汤姆1 小时前
C++ 红黑树
数据结构·c++
一直要努力哦2 小时前
Redis最终篇分布式锁以及数据一致性
数据库·redis·缓存
wqq_9922502773 小时前
ssm面向品牌会员的在线商城小程序
数据库·小程序
绵绵细雨中的乡音3 小时前
C++第28课-布隆过滤器的介绍
c++·哈希算法
C++忠实粉丝3 小时前
计算机网络socket编程(5)_TCP网络编程实现echo_server
网络·c++·网络协议·tcp/ip·计算机网络·算法
胜天半子_王二_王半仙4 小时前
c++源码阅读__smart_ptr__正文阅读
开发语言·c++·开源
程序猿阿伟4 小时前
《C++智能合约与区块链底层交互全解析:构建坚实的去中心化应用桥梁》
c++·区块链·智能合约
沐泽Mu4 小时前
嵌入式学习-C嘎嘎-Day08
开发语言·c++·算法