Small. Fast. Reliable.
Choose any three.

从SQLite 3.4.2迁移到3.5.0

SQLite版本3.5.0(2007-09-04)引入了一个新的OS接口层,该层与所有以前的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. 使用三个新接口来创建备用OS接口: 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操作系统接口层

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

2.1虚拟文件系统对象

SQLite的新OS接口基于名为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模块,以实现自定义的OS层。所述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,则),则无论sqlite3_vfs_register()的makeDflt参数如何,新的VFS都会自动成为默认VFS 。

标准版本包括默认的“ unix”或“ win32” VFS。但是,如果您使用-DOS_OTHER = 1编译时选项,则将在没有默认VFS的情况下构建SQLite。在这种情况下,应用程序必须在调用sqlite3_open()之前至少注册一个VFS 。这是嵌入式应用程序应使用的方法。与其像以前版本的SQLite那样修改SQLite源以插入备用OS层,不如使用-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; / *结构版本号* /
  int szOsFile; / *子类化sqlite3_file的大小* /
  int mxPathname; / *最大文件路径名长度* /
  sqlite3_vfs * pNext; / *下一个注册的VFS * /
  const char * zName; / *此虚拟文​​件系统的名称* /
  无效* pAppData; / *指向特定于应用程序的数据的指针* /
  int(* xOpen)(sqlite3_vfs *,const char * zName,sqlite3_file *,
               int标志,int * pOutFlags);
  int(* xDelete)(sqlite3_vfs *,const char * zName,int syncDir);
  int(* xAccess)(sqlite3_vfs *,const char * zName,int标志);
  int(* xGetTempName)(sqlite3_vfs *,char * zOut);
  int(* xFullPathname)(sqlite3_vfs *,const char * zName,char * zOut);
  无效*(* 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微秒);
  int(* xCurrentTime)(sqlite3_vfs *,double *);
  / *新字段可能会附加在图形版本中。iVersion
  **值会在发生这种情况时增加。* /
};

要创建新的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时要与之比较的名称。

SQLite核心未使用pAppData指针。该指针可用于存储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()调用添加以下标志之一:

文件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参数为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指针。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),并且是分数儒略日数。

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大小);
  int(* xSync)(sqlite3_file *,int标志);
  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 *);
  / *将来的版本中可能会添加其他方法* /
};

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

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继续根据需要扩展文件,以便在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上XSYNC方法返回F_FULLSYNC的ioctl()的 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”参数是整数操作码。第三个参数是通用指针,旨在用作指向结构的指针,该结构可能包含要在其中写入返回值的参数或空间。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. 实现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.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,int N),
  无效* 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 Mutex子系统

从可以在不同线程中同时使用不同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_THREADSAFE = 0标志编译SQLite,则将提供快速但没有真正互斥的无操作互斥体实现。该实现适用于单线程应用程序或仅在单线程中使用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源代码合并,而无需进行更改,从而大大简化了配置管理。最终的系统对高线程设计的容忍度更高。