首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

SQLite Changes From Version 3.4.2 To 3.5.0

SQLite版本3.5.0(2007-09-04)引入了一个新的操作系统接口层,它与SQLite的所有先前版本不兼容。另外,一些现有的接口已经被推广到跨进程内的所有数据库连接而不是线程内的所有连接。本文的目的是详细描述对3.5.0的更改,以便SQLite的以前版本的用户可以判断升级到新版本需要付出哪些努力(如果有的话)。

1.0 Overview Of Changes

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

  1. 操作系统界面层已完全重构:
    1. 未记录的sqlite3_os_switch() 接口已被删除。
    2. SQLITE_ENABLE_REDEF_IO编译时标志不再起作用。I / O程序现在可以重新定义。
    3. 定义了三个用于指定I / O过程的新对象:sqlite3_vfs,sqlite3_file和sqlite3_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源进行自定义修改的用户(例如为嵌入式硬件添加自定义OS层)可能会发现这些更改会产生更大的影响。另一方面,这些更改的一个重要目标是使定制SQLite更容易在不同的操作系统上使用。

2.0 The OS Interface Layer

如果您的系统为SQLite定义了自定义操作系统界面,或者您正在使用未公开的sqlite3_os_switch()界面,那么您需要进行修改才能升级到SQLite版本3.5.0。乍一看这可能看起来很痛苦。但是当您仔细观察时,您可能会发现,通过新的SQLite界面,您的更改变得更小,更易于理解和管理。现在您的更改很可能会与SQLite融合无缝地协同工作。您将不再需要对代码SQLite源代码进行任何更改。所有更改都可以通过应用程序代码来实现,并且您可以链接到标准的,未经修改的SQLite合并版本。此外,操作系统接口层以前没有记录,现在是SQLite官方支持接口。

2.1 The Virtual File System Object

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 Registering New VFS Objects

适用于Unix或Windows的SQLite标准版本随附一个名为“unix”或“win32”的VFS。这一个VFS也是默认的。因此,如果您使用传统的开放式功能,则所有内容都将继续像以前一样运行。变化是现在应用程序可以灵活地添加新的VFS模块来实现定制的OS层。sqlite3_vfs_register()API可用于告诉SQLite关于一个或多个应用程序定义的VFS模块:

代码语言:javascript
复制
int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);

应用程序可以随时调用sqlite3_vfs_register(),当然在使用VFS之前需要注册。第一个参数是指向应用程序已准备好的自定义VFS对象的指针。第二个参数是真的,使新的VFS成为默认的VFS,以便传统的sqlite3_open() 和sqlite3_open16()API使用它。如果新的VFS不是默认的,那么你可能必须使用新的sqlite3_open_v2()API来使用它。但是,请注意,如果新的VFS是SQLite唯一已知的VFS(如果SQLite编译时没有其常规的默认VFS,或者使用sqlite3_vfs_unregister()删除了预编译的默认VFS),则新的VFS自动成为默认的VFS,而不管sqlite3_vfs_register()的makeDflt参数。

标准版本包括默认的“unix”或“win32”VFSes。但是,如果您使用-DOS_OTHER = 1编译时选项,则SQLite将在没有默认VFS的情况下生成。在这种情况下,应用程序必须在调用sqlite3_open()QLite源代码插入一个替代的操作系统层,而是用-DOS_OTHER = 1选项编译一个未经修改的SQLite源文件(最好是合并),然后调用sqlite3_vfs_register()来定义接口到创建任何数据库连接之前的底层文件系统。

2.1.2 Additional Control Over VFS Objects

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

代码语言:javascript
复制
int sqlite3_vfs_unregister(sqlite3_vfs*);

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

代码语言:javascript
复制
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);

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

2.1.3 Modifications Of Existing VFSes

一旦VFS被注册,它不应该被修改。如果需要改变行为,应该注册一个新的VFS。也许应用程序可以使用sqlite3_vfs_find()来定位旧的VFS,将旧的VFS复制到新的sqlite3_vfs对象中,对新的VFS进行所需的修改,取消注册旧的VFS,然后在其VFS中注册新的VFS地点。即使未注册,现有数据库连接仍会继续使用旧的VFS,但新的数据库连接将使用新的VFS。

2.1.4 The VFS Object

VFS对象是以下结构的一个实例:

代码语言:javascript
复制
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能够处理的最长路径名。在大多数情况下,几百个是很有价值的。

SQLite内部使用pNext字段。具体来说,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(),则标志是SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE。如果xOpen()以只读方式打开文件,那么它将* pOutFlags设置为包含SQLITE_OPEN_READONLY。可以设置* pOutFlags中的其他位。根据打开的对象,SQLite还会将以下标志之一添加到xOpen()调用中:

  • SQLITE_OPEN_MAIN_DB
  • SQLITE_OPEN_MAIN_JOURNAL
  • SQLITE_OPEN_TEMP_DB
  • SQLITE_OPEN_TEMP_JOURNAL
  • SQLITE_OPEN_TRANSIENT_DB
  • SQLITE_OPEN_SUBJOURNAL
  • SQLITE_OPEN_MASTER_JOURNAL

文件I / O实现可以使用对象类型标志来改变它处理文件的方式。例如,不关心崩溃恢复或回滚的应用程序可能会使日志文件的打开为空操作。写给这本杂志也是没有用的。任何尝试读取日志都会返回SQLITE_IOERR。或者实现可能会认识到数据库文件将以随机顺序进行页对齐的扇区读取和写入,并相应地设置其I / O子系统。SQLite可能还会将以下标志之一添加到xOpen方法中:

  • SQLITE_OPEN_DELETEONCLOSE
  • SQLITE_OPEN_EXCLUSIVE

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参数为true,则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指针。

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),是一个分数的儒略日数。

2.1.5 The Open File Object

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

代码语言:javascript
复制
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对象是一个包含读取,写入和处理文件方法指针的结构。这个对象定义如下:

代码语言:javascript
复制
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应该确保文件在开始写入之前用0扩展到最多10个字节。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按顺序支持五个级别的文件锁定:

  • SQLITE_LOCK_NONE
  • SQLITE_LOCK_SHARED
  • SQLITE_LOCK_RESERVED
  • SQLITE_LOCK_PENDING
  • SQLITE_LOCK_EXCLUSIVE

只要符合本段的其他要求,底层实现就可以支持这些锁定级别的一些子集。锁定级别被指定为xLock和xUnlock的第二个参数。xLock方法将锁定级别增加到指定的锁定级别或更高。xUnlock方法将锁定级别降低到不低于指定的级别。SQLITE_LOCK_NONE表示该文件已解锁。SQLITE_LOCK_SHARED授予读取文件的权限。多个数据库连接可以同时保存SQLITE_LOCK_SHARED。SQLITE_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中返回问题。xLock在成功时返回SQLITE_OK,如果无法获取锁,则返回SQLITE_BUSY,否则返回SQLITE_IOERR_RDLOCK。xUnlock方法在成功时返回SQLITE_OK,在SQLITE_IOERR_UNLOCK中返回问题。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可用于提高性能的任何特殊属性。允许的返回值是以下值的按位OR:

  • SQLITE_IOCAP_ATOMIC
  • SQLITE_IOCAP_ATOMIC512
  • SQLITE_IOCAP_ATOMIC1K
  • SQLITE_IOCAP_ATOMIC2K
  • SQLITE_IOCAP_ATOMIC4K
  • SQLITE_IOCAP_ATOMIC8K
  • SQLITE_IOCAP_ATOMIC16K
  • SQLITE_IOCAP_ATOMIC32K
  • SQLITE_IOCAP_ATOMIC64K
  • SQLITE_IOCAP_SAFE_APPEND
  • SQLITE_IOCAP_SEQUENTIAL

SQLITE_IOCAP_ATOMIC位意味着对此设备的所有写入都是原子的,因为整个写入都发生或者不会发生。其他SQLITE_IOCAP_ATOMIC nnn值指示写入指定大小的对齐块是原子性的。SQLITE_IOCAP_SAFE_APPEND表示在用新数据扩展文件时,首先写入新数据,然后更新文件大小。所以如果发生电源故障,文件可能不会随机扩展。SQLITE_IOCAP_SEQUENTIAL位表示所有写入都按照它们发出的顺序发生,并且不会被底层文件系统重新排序。

2.1.6 Checklist For Constructing A New 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 The Memory Allocation Subsystem

从版本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()提供实现。而且他们还必须实现第四个功能:

代码语言:javascript
复制
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设置为空指针来禁用回调。

回调参数是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中的内置内存分配器还提供了以下附加接口:

代码语言:javascript
复制
sqlite3_int64 sqlite3_memory_used(void);
sqlite3_int64 sqlite3_memory_highwater(int resetFlag);

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

4.0 The Mutex Subsystem

SQLite一直是线程安全的,因为在不同的线程中同时使用不同的SQLite数据库连接是安全的。约束条件是同一个数据库连接不能同时在两个独立的线程中使用。SQLite版本3.5.0放宽了这个约束。

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

代码语言:javascript
复制
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_FAST或SQLITE_MUTEX_RECURSIVE。如果底层系统不提供非递归互斥体,则在这种情况下可以用递归互斥体代替。sqlite3_mutex_alloc()的参数也可以是一个常量,指定几个静态互斥量之一:

  • SQLITE_MUTEX_STATIC_MASTER
  • SQLITE_MUTEX_STATIC_MEM
  • SQLITE_MUTEX_STATIC_MEM2
  • SQLITE_MUTEX_STATIC_PRNG
  • SQLITE_MUTEX_STATIC_LRU

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

应该使用sqlite3_mutex_free()例程来释放非静态互斥锁。如果一个静态互斥量传递给这个例程,那么这个行为是不确定的。

如果另一个线程已经存在,sqlite3_mutex_enter()会尝试输入互斥锁和块。sqlite3_mutex_try()尝试成功时返回SQLITE_OK,如果另一个线程已经存在,SQLITE_BUSY将返回SQLITE_BUSY。sqlite3_mutex_leave()退出一个互斥锁。互斥量一直保持到退出次数与入口数相匹配。如果在线程当前没有持有的互斥体上调用sqlite3_mutex_leave(),则行为是未定义的。如果任何例程被调用去分配互斥量,那么行为是不确定的。

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

嵌入式应用程序可能希望提供自己的互斥体实现。如果使用-DSQLITE_MUTEX_APPDEF = 1编译时标志编译SQLite,则SQLite内核不提供互斥子系统,并且与上述接口相匹配的互斥子系统必须由与SQLite链接的应用程序提供。

5.0 Other Interface Changes

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 Summary

从SQLite 3.4.2到3.5.0的过渡是一个重大改变。SQLite内核中的每个源代码文件都必须进行广泛的修改。这个改变在C界面中引入了一些小的不兼容性。但我们认为从3.4.2到3.5.0的过渡所带来的好处远远超过了移植的痛苦。新的VFS层现在已经很好的定义和稳定,并且应该简化未来的定制。VFS层和可分离的内存分配器和互斥子系统允许在不改变的情况下在嵌入式项目中使用标准的SQLite源代码合并,极大地简化了配置管理。最终的系统对高度线程化的设计更加宽容。

代码语言:javascript
复制
 SQLite在公共领域。

扫码关注腾讯云开发者

领取腾讯云代金券