Small. Fast. Reliable.
Choose any three.
WAL模式文件格式

本文档介绍了有关如何在UNIX和Windows上实现WAL模式的底层细节。

单独的文件格式描述提供了有关WAL模式下使用的数据库文件和写头日志文件的结构的详细信息 。但是有意省略了锁定协议的细节和WAL-index格式的细节,因为这些细节留给各个VFS实现酌情决定。本文档填充了UNIX和Windows VFS的那些缺失的详细信息。

为了完整起见,某些高级格式化信息包含在文件格式文档中,而当它与WAL模式处理有关时,此处将复制其他内容。

1.磁盘上的文件

处于活动状态时,WAL模式数据库的状态由三个单独的文件描述:

  1. 具有任意名称“ X”的主数据库文件。
  2. 预写日志文件,通常命名为“ X-wal”。
  3. wal-index文件,通常命名为“ X-shm”。

1.1。主数据库文件

主数据库文件的格式如 文件格式文档中所述。主数据库中偏移量18和19的文件格式版本号都必须均为2,以指示数据库处于WAL模式。主数据库可以具有基础文件系统允许的任意名称。尽管“ .db”,“。sqlite”和“ .sqlite3”似乎是流行的选择,但不需要特殊的文件后缀。

1.2。预写日志或“ -wal”文件

预写日志或“ wal”文件是前滚日志,记录已提交但尚未应用于主数据库的事务。有关wal文件格式的详细信息,请参见主文件格式 文档的WAL格式小节。通过在主数据库文件名的末尾附加四个字符“ -wal”来命名wal文件。除8 + 3文件系统外,不允许使用此类名称,在这种情况下,文件后缀将更改为“ .WAL”。但是随着8 + 3文件系统越来越稀有,通常可以忽略这种例外情况。

1.3。Wal-Index或“ -shm”文件

wal-index文件或“ shm”文件实际上并未用作文件。而是,各个数据库客户端会映射shm文件,并将其用作共享内存以协调对数据库的访问,并用作高速缓存以在wal文件中快速定位帧。shm文件的名称是主数据库文件名,后跟四个字符“ -shm”。或者,对于8 + 3文件系统,shm文件是主数据库文件,后缀更改为“ .SHM”。

shm不包含任何数据库内容,并且崩溃后无需恢复该数据库。因此,连接到静态数据库的第一个客户端通常会截断shm文件(如果存在)。由于不需要在崩溃时保留shm文件的内容,因此永不fsync()将shm文件存储到磁盘。实际上,如果存在一种机制,SQLite可以通过该机制告诉操作系统从不将shm文件持久保存在磁盘上,而是始终将其保存在缓存中,则SQLite将使用该机制来避免与shm文件关联的任何不必要的磁盘I / O。 。但是,在标准posix中不存在这样的机制。

由于shm仅用于协调并发客户端之间的访问,因此,如果 设置了独占锁定模式,则会将shm文件省略,以进行优化。当独家锁定模式被设置时,SQLite使用堆存储器代替存储器映射SHM文件。

1.4。文件生命周期

在积极使用WAL模式数据库时,以上三个文件通常都存在。除非设置了独占锁定模式,否则将省略Wal-Index文件 。

如果最后一个使用数据库的客户端通过调用sqlite3_close()完全关闭,则将自动运行检查点,以便将所有信息从wal文件传输到主数据库中,并且shm文件和wal文件都将取消链接。因此,当没有任何客户端使用数据库时,通常情况是磁盘上仅存在主数据库文件。但是,如果最后一个客户端在关闭之前未调用sqlite3_close(),或者如果最后一个断开连接的客户端是只读客户端,则不会进行最终的清理操作,并且磁盘上可能仍然存在shm和wal文件即使不使用数据库也是如此。

1.5。变化

设置PRAGMAlocking_mode = EXCLUSIVE(排他锁定模式)时,仅允许单个客户端一次打开数据库。由于只有一个客户端可以使用该数据库,因此将省略shm文件。单个客户端使用堆内存中的缓冲区代替内存映射的shm文件。

如果读/写客户端在关闭之前调用 sqlite3_file_controlSQLITE_FCNTL_PERSIST_WAL),则在关闭时仍会运行检查点,但不会删除shm文件和wal文件。这允许后续的只读客户端连接到数据库并读取数据库。

2. WAL索引文件格式

WAL-index或“ shm”文件用于协调多个客户端对数据库的访问,并用作缓存来帮助客户端快速定位wal文件中的帧。

由于shm文件不参与恢复,因此shm文件不需要与机器字节顺序无关。因此,shm文件中的数值以主机的本机字节顺序写入,而不是像使用主数据库文件和wal文件那样转换为特定的跨平台字节顺序。

shm文件由一个或多个哈希表组成,其中每个哈希表的大小为32768字节。唯一的区别是,在第一个哈希表的开头雕刻了一个136字节的标头,因此第一个哈希表的大小仅为32632字节。shm文件的总大小始终是32768的倍数。在大多数情况下,shm文件的总大小恰好是32768字节。如果wal文件变得非常大(超过4079帧),则shm文件仅需要超出单个哈希表即可。由于默认的自动检查点阈值为1000,因此WAL文件很少达到使shm文件增长所需的4079阈值。

2.1。WAL索引标头

shm文件的前136个字节为标头。shm标头具有以下三个主要部分:

WAL索引标头部门
字节数描述
0..47WAL索引信息的第一份副本
48..95WAL索引信息的第二份副本
96..135检查点信息和锁

除了从WAL标头复制的盐值之外,shm标头的各个字段都是主机本机字节顺序中的无符号整数。盐值是WAL标头中的精确副本,并且采用WAL文件使用的字节顺序。整数的大小可以是8、16、32或64位。shm标头的各个字段的详细细分如下:

WAL索引标头详细信息
字节数名称意义
0..3版本 WAL索引格式的版本号。始终为3007000。
4..7  未使用的填充空间。必须为零。
8..11我改变 无符号整数计数器,每笔交易都会增加
12在里面 “ isInit”标志。初始化shm文件时为1。
13bigEndCksum 如果WAL文件使用大端校验和,则为true。如果WAL使用低位字节校验和,则为0。
14..15页面 数据库页面大小(以字节为单位);如果页面大小为65536,则为1。
16..19mxFrame WAL文件中有效和已提交帧的数量。
20..23页数 数据库文件的大小(以页为单位)。
24..31aFrameCksum WAL文件中最后一帧的校验和。
32..39 从WAL文件头复制的两个盐值。这些值是WAL文件的字节顺序,可能与计算机的本机字节顺序不同。
40..47求和 此标头的字节0到39上的校验和。
48..95  此标头的字节0到47的副本。
96..99n回填 先前的检查点已回填到数据库中的WAL帧数
100..119读标记[0..4] 五个“读标记”。每个读取标记是一个32位无符号整数(4个字节)。
120..127  预留8个文件锁的未使用空间。
128..132nBackfillAttempted 尝试回填但未成功回填的WAL帧数。
132..136  保留未使用的空间以进一步扩展。

2.1.1。mxFrame字段

偏移量16处的32位无符号整数(并在偏移量64处重复)是WAL中有效帧的数量。因为WAL帧从1开始编号,所以mxFrame也是WAL中最后一个有效提交帧的索引。提交帧是在帧头的字节4到7中具有非零“数据库大小”值的帧,它指示事务结束。

当mxFrame字段为零时,表明WAL为空,所有内容应直接从数据库文件获取。

当mxFrame等于nBackfill时,表明WAL中的所有内容都已写回到数据库中。在这种情况下,所有内容都可以直接从数据库中读取。此外,如果没有其他连接在WAL_READ_LOCK(N)上保持N> 0的锁,则下一个编写器可以自由重置WAL

mxFrame值始终大于或等于 nBackfill和nBackfillAttempted。

2.1.2。nBackfill字段

WAL-index标头中偏移量128处的32位无符号整数称为“ nBackfill”。该字段保存WAL文件中已复制回主数据库的帧数。

nBackfill编号永远不会大于mxFrame。当nBackfill等于mxFrame时,这意味着WAL内容已完全写回到数据库中,并且如果N> 0的WAL_READ_LOCK(N)上没有任何锁,则可以重置WAL

仅在保持WAL_CKPT_LOCK的情况下才能增加nBackfill。但是,在WAL重置过程中,nBackfill会更改为零,并且在保持WAL_WRITE_LOCK时会发生这种情况。

2.1.3。WAL锁

标头中预留了八个字节的空间,以使用sqlite3_io_methods 对象中的xShmLock()方法支持文件锁定。由于某些VFS(例如Windows)可能使用强制性文件锁来实现锁,因此SQLite永远不会读取或写入这八个字节。

这些是支持的八种锁:

由xShmLock()控制的WAL索引锁
名称抵消
xShmLock文件
WAL_WRITE_LOCK 0 120
WAL_CKPT_LOCK 1个 121
WAL_RECOVER_LOCK 2个 122
WAL_READ_LOCK(0) 3 123
WAL_READ_LOCK(1) 4 124
WAL_READ_LOCK(2) 5 125
WAL_READ_LOCK(3) 6 126
WAL_READ_LOCK(4) 7 127

TBD:有关标题的更多信息

2.2。WAL索引哈希表

shm文件中的哈希表旨在快速回答以下问题:

FindFrame(P,M):给定页码P和最大WAL帧索引M,返回不超过M的页面P的最大WAL帧索引,如果没有不超过P的帧,则返回NULL M.

令数据类型“ u8”,“ u16”和“ u32”分别表示长度为8位,16位和32位的无符号整数。然后,将shm文件的第一个32768字节单元组织如下:

u8 aWalIndexHeader [136];
u32 aPgno [4062];
u16 aHash [8192];

shm文件的第二个以及所有后续的32768字节单位如下所示:

u32 aPgno [4096];
u16 aHash [8192];

aPgno条目共同记录存储在WAL文件的所有帧中的数据库页号。第一个哈希表上的aPgno [0]条目记录存储在WAL文件的第一帧中的数据库页号。第一个哈希表中的aPgno [i]条目是WAL文件中第i帧的数据库页号。第二个哈希表的aPgno [k]条目是WAL文件中第(k + 4062)帧的数据库页号。shm文件中第n个32768字节哈希表的aPgno [k]条目(对于n> 1)保存存储在第(k + 4062 + 4096 *(n-2))帧中的数据库页号WAL文件。

这是描述aPgno值的略有不同的方法:如果您将所有aPgno值都视为连续数组,则存储在WAL文件的第i帧中的数据库页号将存储在aPgno [i]中。当然,aPgno不是连续数组。前4062个条目位于shm文件的前32768字节单元中,后续值位于shm文件的稍后单元中的4096个条目块中。

一种计算FindFrame(P,M)的方法是从第M个条目开始扫描aPgno数组,并向后开始搜索,并在aPgno [J] == P处返回J。这样的算法将起作用,并且比在整个WAL文件中搜索页码为P的最新帧要快。但是,仍然可以通过使用aHash结构来使搜索更快。

使用以下哈希函数将数据库页号P映射到哈希值:

h =(P * 383)%8192

此函数将每个页码映射为0到8191(含)之间的整数。每个32768字节shm文件单元的aHash字段将P值映射到同一单元的aPgno字段的索引,如下所示:

  1. 计算哈希值:h = P * 383
  2. 令X为连续整数{h,h + 1,h + 2,...,h + N}的最大集合,这样对于X中的每个j,aPgno [j%8192]!= 0。如果aPgno [h%8192] == 0,则X集将为空。通过从值h%8192开始并将x添加h%8192并递增h直到遇到第一个为零的aPgno [h%8192]条目,可以轻松地计算X集。
  3. 集合X包含shm文件当前32768字节单位中每个条目的aPgno索引,这可能是FindFrame(P,M)函数的解决方案。必须分别检查每个条目,以确保aPgno值为P,并且帧号不超过M。通过这两个测试的最大帧号就是答案。

aPgno数组中的每个条目在aHash数组中都有一个对应的条目。aHash中的可用插槽比aPgno中的可用插槽更多。aHash中未使用的插槽填充为零。并且由于保证aHash中有未使用的时隙,因此这意味着可以保证计算X的循环终止。X的预期大小小于2。最坏的情况是X与aPgno中的条目数相同,在这种情况下,算法的运行速度与aPgno的线性扫描大致相同。但是那种最坏情况下的性能却极为罕见。通常,X的大小会很小,并且使用aHash数组可以使人们更快地计算FindFrame(P,M)。

这是描述哈希查找算法的另一种方法:从h =(P * 383)%8192开始,查看aHash [h]和后续的条目,当h达到8192时环绕到零,直到找到带有aHash [h] == 0。页数为P的所有aPgno条目将具有一个索引,该索引是因此计算出的aHash [h]值之一。但是,并非所有计算出的aHash [h]值都符合匹配条件,因此您必须独立检查它们。之所以会获得速度优势,是因为通常这组h值非常小。

请注意,shm文件的每个32768字节单元都有其自己的aHash和aPgno阵列。单个单元的aHash数组仅有助于查找同一单元中的aPgno条目。整个FindFrame(P,M)函数需要从最新的单元开始进行哈希查找,然后再回溯到最旧的单元,直到找到答案为止。

2.3。锁矩阵

访问被同时使用由所述的XLOCK和xUnlock方法控制的遗留DELETE模式锁在WAL模式协调sqlite3_io_methods 对象和WAL锁具由的xShmLock方法控制 sqlite3_io_methods对象。

从概念上讲,只有一个DELETE模式锁。单个数据库连接的DELETE模式锁可以恰好处于以下状态之一:

  1. SQLITE_LOCK_NONE(已解锁)
  2. SQLITE_LOCK_SHARED(正在读取)
  3. SQLITE_LOCK_RESERVED(正在读取,正在等待写入)
  4. SQLITE_LOCK_PENDING(新阅读器被阻止,正在等待写入)
  5. SQLITE_LOCK_EXCLUSIVE(写)

DELETE模式锁存储在主数据库文件的锁字节页面上。仅SQLITE_LOCK_SHARED和SQLITE_LOCK_EXCLUSIVE是WAL模式数据库的因素。其他锁定状态在回滚模式下使用,但在WAL模式下不使用。

WAL模式锁如上所述。

2.3.1。如何使用各种锁

以下规则显示了如何使用每个锁。

2.3.2。需要锁定的操作以及将这些操作锁定的操作

3.恢复

恢复是重建WAL索引以使其与WAL同步的过程。

恢复由第一个连接到WAL模式数据库的线程运行。恢复将还原WAL索引,以便它准确地描述WAL文件。如果在第一个线程连接到数据库时不存在WAL文件,则没有要恢复的内容,但是恢复过程仍在运行以初始化WAL索引。

如果WAL-index是作为内存映射文件实现的,并且该文件对要连接的第一个线程是只读的,则该线程将创建一个私有堆内存erazt WAL-index并运行恢复例程以填充该私有WAL -指数。产生相同的数据,但是将其私有保存,而不是写入公共共享存储区。

恢复是通过从头到尾对WAL进行一次遍历来进行的。读取WAL时,将在WAL的每个帧上验证校验和。扫描在文件末尾或第一个无效校验和处停止。该mxFrame字段设置为在WAL最后一个有效提交框架的指标。由于WAL帧号是从1开始索引的,因此mxFrame也是WAL中有效帧的数目。“提交帧”是在帧头的字节4到7中具有非零值的帧。由于恢复过程无法知道以前可能已经将WAL的多少帧复制回数据库,因此它将nBackfill值初始化为零。

在全局共享内存WAL索引的恢复过程中,排他锁通过WAL_READ_LOCK(4)保留在WAL_WRITE_LOCK,WAL_CKPT_LOCK,WAL_RECOVER_LOCK和WAL_READ_LOCK(1)上。换句话说,除WAL_READ_LOCK(0)以外,所有与WAL-index关联的锁均被保留。这样可以防止其他任何线程在恢复完成之前写入数据库和读取WAL中保留的任何事务。