Small. Fast. Reliable.
Choose any three.
数据库文件格式

本文档描述并定义了自3.0.0(2004-06-18)版本以来的所有SQLite版本所使用的磁盘数据库文件格式.

1. The Database File

SQLite数据库的完整状态通常包含在磁盘上称为"主数据库文件"的单个文件中.

在事务期间,SQLite将其他信息存储在称为"回滚日志"的第二个文件中;如果SQLite处于WAL模式 ,则该文件为预写日志文件.

1.1. Hot Journals

如果应用程序或主机在事务完成之前崩溃,则回滚日志或预写日志包含将主数据库文件还原到一致状态所需的信息. 当回滚日志或预写日志包含恢复数据库状态所需的信息时,它们被称为"热日志"或"热WAL文件". 热日志和WAL文件仅是错误恢复方案中的一个因素,因此并不常见,但它们是SQLite数据库状态的一部分,因此不能忽略. 本文档定义了回滚日志和预写日志文件的格式,但是重点是主数据库文件.

1.2. Pages

主数据库文件包含一个或多个页面. 页面的大小是512到65536(含)之间的2的幂. 同一数据库内的所有页面大小均相同. 数据库文件的页面大小由2个字节的整数确定,该整数位于距数据库文件开头16字节的偏移处.

页编号以1开始的最大页数量是2147483646(二月31日至 2 ). 最小大小的SQLite数据库是一个512字节的页面. 最大大小的数据库为2147483646页,每页65536字节,或140,737,488,224,256字节(约140 TB). 通常,SQLite在达到自己的内部大小限制之前,将达到基础文件系统或磁盘硬件的最大文件大小限制.

尽管已知在生产中存在数TB的SQLite数据库,但通常使用的SQLite数据库的大小范围从几千字节到几GB.

在任何时间点,主数据库中的每个页面都有一次使用,这是以下之一:

从主数据库文件进行的所有读取和写入均始于页面边界,并且所有写入均为页面大小的整数. 读取通常也是页面大小的整数,一个例外是,第一次打开数据库时,数据库文件的前100个字节(数据库文件头)被读取为子页面大小单位.

在修改数据库的任何信息页面之前,该页面的原始未修改内容都将写入回滚日志中. 如果事务中断并且需要回滚,则可以使用回滚日志将数据库还原到其原始状态. 自由列表叶子页不包含回滚时需要还原的信息,因此,为了减少磁盘I / O,它们不会在修改之前写入日志中.

1.3. The Database Header

数据库文件的前100个字节构成数据库文件头. 数据库文件头分为如下表所示的字段. 数据库文件头中的所有多字节字段都以最高有效字节在前(big-endian)存储.

数据库头格式
OffsetSizeDescription
016 标题字符串:" SQLite格式3 \ 000"
162 数据库页面大小(以字节为单位). 必须是512到32768(含)之间的2的幂,或者值1代表65536的页面大小.
181 文件格式写入版本. 1表示旧版; WAL为2.
191 文件格式读取版本. 1表示旧版; WAL为2.
201 每页末尾未使用的"保留"空间的字节数. 通常为0.
211 最大嵌入式有效负载分数. 必须为64.
221 最小嵌入式有效负载分数. 必须为32.
231 叶子有效载荷分数. 必须为32.
244 文件更改计数器.
284 数据库文件的大小(以页为单位). "页眉中的数据库大小".
324 第一个空闲列表主干页面的页码.
364 自由列表页面总数.
404 模式cookie.
444 模式格式编号. 支持的架构格式为1、2、3和4.
484 默认页面缓存大小.
524 在自动真空或增量真空模式下,最大的根b树页的页号,否则为零.
564 数据库文本编码. 值1表示UTF-8. 值2表示UTF-16le. 值3表示UTF-16be.
604user_version pragma读取和设置的"用户版本".
644 增量真空模式为真(非零). 否则为假(零).
684 PRAGMA application_id设置的"应用程序ID".
7220 保留用于扩展. 必须为零.
924 The version-valid-for number.
964 SQLITE_VERSION_NUMBER

1.3.1. Magic Header String

每个有效的SQLite数据库文件都以以下16个字节(以十六进制表示)开头:53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 3300.此字节序列对应于UTF-8字符串" SQLite format 3",包括末尾的nul终止符.

1.3.2. Page Size

从偏移量16开始的两个字节的值确定数据库的页面大小. 对于SQLite 3.7.0.1(2010-08-04)及更早版本,此值解释为big-endian整数,并且必须是512到32768(含)之间的2的幂. 从SQLite 版本3.7.1 (2010-08-23)开始,支持65536字节的页面大小. 值65536将不适合两个字节的整数,因此要指定65536字节的页面大小,偏移量16处的值为0x00 0x01. 此值可以解释为big-endian 1,并且可以视为表示65536页大小的幻数. 或者可以将两个字节的字段视为一个小端数字,并说它表示页面大小除以256.页面大小字段的这两种解释是等效的.

1.3.3. File format version numbers

偏移量18和19处的文件格式写入版本和文件格式读取版本旨在允许在将来的SQLite版本中增强文件格式. 在当前版本的SQLite中,对于回滚日志记录模式,这两个值均为1;对于WAL日志记录模式,这两个值均为2. 如果编码为当前文件格式规范的SQLite版本遇到读取版本为1或2但写入版本大于2的数据库文件,则必须将该数据库文件视为只读. 如果遇到读取版本大于2的数据库文件,则无法读取或写入该数据库.

1.3.4. Reserved bytes per page

SQLite has the ability to set aside a small number of extra bytes at the end of every page for use by extensions. These extra bytes are used, for example, by the SQLite Encryption Extension to store a nonce and/or cryptographic checksum associated with each page. The "reserved space" size in the 1-byte integer at offset 20 is the number of bytes of space at the end of each page to reserve for extensions. This value is usually 0. The value can be odd.

数据库页面的"可用大小"是标题中偏移量为16的2字节整数指定的页面大小减去标题中偏移量为20的1字节整数中记录的"保留"空间大小. 页面的可用大小可能是奇数. 但是,可用大小不能小于480.换句话说,如果页面大小是512,则保留空间大小不能超过32.

1.3.5. Payload fractions

最大和最小嵌入式有效负载分数和叶有效负载分数值必须分别为64、32和32.这些值最初旨在用作可用于修改b树算法的存储格式的可调参数. 但是,不支持该功能,并且当前没有将来增加支持的计划. 因此,这三个字节固定为指定的值.

1.3.6. File change counter

文件更改计数器是偏移量24处的4字节大尾数整数,在修改数据库文件后对其进行解锁时,该计数器将递增. 当两个或多个进程正在读取同一数据库文件时,每个进程可以通过监视更改计数器来检测其他进程的数据库更改. 另一个进程修改数据库后,一个进程通常将希望刷新其数据库页面缓存,因为该缓存已过时. 文件更改计数器可简化此操作.

在WAL模式下,使用wal-index检测对数据库的更改,因此不需要更改计数器. 因此,在WAL模式下,每个事务上的更改计数器可能不会增加.

1.3.7. In-header database size

The 4-byte big-endian integer at offset 28 into the header stores the size of the database file in pages. If this in-header datasize size is not valid (see the next paragraph), then the database size is computed by looking at the actual size of the database file. Older versions of SQLite ignored the in-header database size and used the actual file size exclusively. Newer versions of SQLite use the in-header database size if it is available but fall back to the actual file size if the in-header database size is not valid.

头内数据库大小只有在非零且偏移量24处的4字节更改计数器与偏移量92处的4字节版本有效号完全匹配时,才被认为是有效的.头内数据库当仅使用SQLite的最新版本3.7.0(2010-07-21)和更高版本修改数据库时,size始终有效. 如果SQLite的旧版本写入数据库,它将不知道更新头内数据库大小,因此头内数据库大小可能不正确. 但是,SQLite的旧版本还将在偏移量92处保留版本有效号,因此它不会与更改计数器匹配. 因此,可以通过观察更改计数器何时与版本有效期号不匹配来检测(并忽略)无效的头内数据库大小.

1.3.8. Free page list

数据库文件中未使用的页面存储在空闲列表中. 偏移量32处的4字节big-endian整数存储空闲列表第一页的页号,如果空闲列表为空,则为0. 偏移量为36的4字节big-endian整数存储自由列表上的页面总数.

模式cookie是偏移量40处的4字节大尾数整数,每当数据库模式更改时,该cookie就会递增. 准备好的语句是针对特定版本的数据库模式进行编译的. 当数据库模式更改时,必须重新准备该语句. 当准备好的语句运行时,它首先检查架构cookie,以确保该值与准备该语句时的值相同;如果架构cookie已更改,则该语句将自动重新准备并重新运行,或者由于SQLITE_SCHEMA错误而中止.

1.3.10. Schema format number

模式格式号是偏移量44处的4字节大尾数整数.模式格式号与偏移量18和19处的文件格式读取和写入版本号相似,除了模式格式号引用高级SQL.格式化,而不是低级b树格式化. 当前定义了四个模式格式编号:

  1. 回到版本3.0.0 (2004-06-18),SQLite的所有版本都可以理解格式1.
  2. 格式2增加了同一表中的行具有可变数量的列的能力,以便支持ALTER TABLE ... ADD COLUMN功能. 在2005-02-20的SQLite 版本3.1.3中添加了对读写格式2的支持.
  3. 格式3增加了ALTER TABLE ... ADD COLUMN添加的额外列的功能,使其具有非NULL默认值. 在2005-03-11的SQLite 版本3.1.4中添加了此功能.
  4. 格式4导致SQLite在索引声明中遵守DESC关键字 . (格式1、2和3的索引中忽略了DESC关键字.)格式4还添加了两个新的布尔记录类型值( 串行类型 8和9). 在2006-01-10的SQLite 3.3.0中添加了对格式4的支持.

默认情况下,SQLite创建的新数据库文件使用格式4. legacy_file_format编译指示可用于使SQLite使用格式1创建新的数据库文件.通过在编译时设置SQLITE_DEFAULT_FILE_FORMAT = 1,可以将格式版本号默认设置为1而不是4.

1.3.11. Suggested cache size

偏移量48处的4字节大尾数有符号整数是数据库文件在页面中建议的高速缓存大小. 该值仅是一个建议,SQLite没有义务兑现它. 整数的绝对值用作建议的大小. 可以使用default_cache_size pragma设置建议的缓存大小.

1.3.12. Incremental vacuum settings

偏移量为52和64的两个4字节的大端整数用于管理auto_vacuum增量 _vacuum模式. 如果偏移量52处的整数为零,则将从数据库文件中省略指针映射(ptrmap)页面,并且不支持auto_vacuum和增量_vacuum. 如果偏移量52处的整数不为零,则它是数据库文件中最大根页的页码,数据库文件将包含ptrmap页,并且模式必须为auto_vacuum或增量_vacuum. 在后一种情况下,偏移量64处的整数对于incremental_vacuum为true,对于auto_vacuum为false. 如果偏移52处的整数为零,那么偏移64处的整数也必须为零.

1.3.13. Text encoding

偏移量56处的4字节大尾数整数确定用于存储在数据库中的所有文本字符串的编码. 值1表示UTF-8. 值2表示UTF-16le. 值3表示UTF-16be. 不允许其他值. sqlite3.h头文件将C预处理器宏SQLITE_UTF8定义为1,将SQLITE_UTF16LE定义为2,将SQLITE_UTF16BE定义为3,以代替用于文本编码的数字代码.

1.3.14. User version number

偏移量60处的4字节big-endian整数是用户版本,由user_version pragma设置和查询. SQLite不使用用户版本.

1.3.15. Application ID

偏移量68处的4字节大尾数整数是"应用程序ID",可以通过PRAGMA application_id命令设置该命令,以将数据库标识为属于特定应用程序或与特定应用程序相关联. 该应用程序ID用于用作应用程序文件格式的数据库文件. 诸如file(1)之类的实用程序可以使用应用程序ID来确定特定的文件类型,而不仅仅是报告" SQLite3数据库". 通过查询SQLite源存储库中的magic.txt文件,可以看到已分配的应用程序ID的列表.

1.3.16. Write library version number and version-valid-for number

偏移量为96的4字节大尾数整数存储最近修改数据库文件的SQLite库的SQLITE_VERSION_NUMBER值. 偏移量92处的4字节大尾数整数是存储版本号时更改计数器的值. 偏移量92处的整数表示版本号对哪个交易有效,有时称为"版本有效号".

1.3.17. Header space reserved for expansion

数据库文件头的所有其他字节都保留用于将来扩展,并且必须设置为零.

1.4. The Lock-Byte Page

锁定字节页面是数据库文件的单个页面,其中包含1073741824和1073742335(含)之间的偏移量的字节. 大小小于或等于1073741824字节的数据库文件不包含锁定字节页面. 大于1073741824的数据库文件仅包含一个锁定字节页面.

保留了锁定字节页面,以供操作系统特定的VFS实现在实现数据库文件锁定原语时使用. SQLite不使用锁定字节页面. 尽管特定于操作系统的VFS实现可能会根据底层系统的需要和倾向选择在锁定字节页上读取或写入字节,但SQLite核心永远不会读取或写入锁定字节页. SQLite内置的unix和win32 VFS实现不会写入锁定字节页,但是其他操作系统的第三方VFS实现可能会写入.

锁定字节页面的产生是由于需要支持Win95,Win95是设计此文件格式时的主要操作系统,并且仅支持强制性文件锁定. 我们所知道的所有现代操作系统都支持咨询文件锁定,因此实际上不再需要锁定字节页面,但是为了向后兼容而保留了该页面.

1.5. The Freelist

数据库文件可能包含一个或多个未处于活动状态的页面. 例如,当从数据库中删除信息时,可能会出现未使用的页面. 未使用的页面存储在空闲列表中,并且在需要其他页面时可以重新使用.

自由列表组织为自由列表主干页面的链接列表,每个主干页面均包含零个或多个自由列表叶子页面的页码.

空闲列表主干页面由4个字节的大端整数数组组成. 数组的大小是页面可用空间中所能容纳的整数. 最小可用空间为480字节,因此数组的长度始终至少为120. 空闲列表主干页面上的第一个整数是列表中下一个空闲列表主干页面的页码;如果这是最后一个空闲列表主干页面,则为零. 空闲列表主干页面上的第二个整数是要跟随的叶子页面指针的数量. 在空闲列表主干页面L上调用第二个整数.如果L大于零,则数组索引在2到L + 1(含)之间的整数包含空闲列表叶子页面的页码.

自由列表叶子页不包含任何信息. SQLite避免读取或写入自由列表叶页,以减少磁盘I / O.

如果自由列表主干页面数组中的最后6个条目中的任何一个包含非零值,则3.6.0之前的SQLite版本(2008-07-16)中的错误导致数据库报告为已损坏. 较新版本的SQLite不会出现此问题. 但是,较新版本的SQLite仍避免使用自由列表主干页面数组中的最后六个条目,以便较新版本的SQLite可以读取较新版本的SQLite创建的数据库文件.

空闲列表页数以4字节的big-endian整数形式存储在数据库标头中,与文件开头的偏移量为36. 数据库标头还将第一个空闲列表主干页的页号存储为距文件开头32个偏移量的4字节big-endian整数.

1.6. B-tree Pages

b树算法在面向页面的存储设备上为密钥/数据存储提供唯一且有序的密钥. 有关b树的背景信息,请参见Knuth, 计算机编程艺术 ,第3卷"排序和搜索",第471-479页. SQLite使用两种b树. Knuth称为" B *-树"的算法将所有数据存储在树的叶子中. SQLite将此各种各样的b树称为"表b树". Knuth简单地称其为" B树"的算法将密钥和数据存储在叶子和内部页面中. 在SQLite实现中,原始的B-Tree算法仅存储键,完全省略了数据,被称为"索引b-树".

b树页面是内部页面或叶子页面. 叶子页包含键,在表b树的情况下,每个键都有关联的数据. 内部页面包含K个键以及指向子b树页面的K + 1个指针. 内部b树页面中的"指针"只是子页面的31位整数页面编号.

将叶子b树的深度定义为1,并将任何内部b树的深度定义为比其任何子树的最大深度大一倍. 在格式正确的数据库中,内部b树的所有子级都具有相同的深度.

在内部b树页面中,指针和键在逻辑上与两端的指针交替. (从概念上理解前一句话-页面内键和指针的实际布局更为复杂,将在后续内容中进行描述.)同一页面内的所有键都是唯一的,并且按逻辑从左起升序排列在右边. (同样,此顺序是逻辑上的,而不是物理上的.页面内键的实际位置是任意的.)对于任何键X,X左侧的指针均指b树页面,其中所有键均小于或等于X的指针是指所有键都大于X的页面.

在内部b树页面中,每个键和其紧邻左侧的指针被组合为一个称为"单元"的结构. 最右边的指针被分开保存. 叶子b树页面没有指针,但是它仍然使用单元结构来保存索引b树的键或表b树的键和内容. 数据也包含在单元格中.

每个b树页面最多具有一个父b树页面. 没有父级的b树页面称为根页面. 根b树页面及其子级闭包构成一个完整的b树. 有可能(而且实际上相当普遍)拥有一个完整的b树,该树包含一个既是叶又是根的页面. 因为有从父母到孩子的指针,所以如果仅知道根页面,则可以定位完整b树的每个页面. 因此,b树通过其根页号来标识.

A b-tree page is either a table b-tree page or an index b-tree page. All pages within each complete b-tree are of the same type: either table or index. There is one table b-trees in the database file for each rowid table in the database schema, including system tables such as sqlite_master. There is one index b-tree in the database file for each index in the schema, including implied indexes created by uniqueness constraints. There are no b-trees associated with virtual tables. Specific virtual table implementations might make use of shadow tables for storage, but those shadow tables will have separate entries in the database schema. WITHOUT ROWID tables use index b-trees rather than a table b-trees, so there is one index b-tree in the database file for each WITHOUT ROWID table. The b-tree corresponding to the sqlite_master table is always a table b-tree and always has a root page of 1. The sqlite_master table contains the root page number for every other table and index in the database file.

表b树中的每个条目都由一个64位带符号整数键和最多2147483647个字节的任意数据组成. (表b树的键对应于该b树实现的SQL表的rowid .)内部表b树仅保留键和指向子级的指针. 所有数据都包含在表的b树叶子中.

索引b树中的每个条目都由一个长度最多2147483647字节的任意键组成,并且没有数据.

将单元的"有效负载"定义为该单元的任意长度部分. 对于索引b树,密钥的长度始终是任意的,因此有效负载就是密钥. 内部表b树页面的单元中没有任意长度的元素,因此这些单元没有有效负载. 表b树的叶子页面包含任意长度的内容,因此对于那些页面上的单元,有效载荷就是内容.

当一个单元的有效负载大小超过某个阈值(稍后定义)时,仅将有效负载的前几个字节存储在b树页面上,剩余的存储在内容溢出页面的链接列表中.

b树页面按以下顺序分为多个区域:

  1. The 100-byte database file header (found on page 1 only)
  2. 8或12字节的b树页头
  3. 单元格指针数组
  4. 未分配空间
  5. 单元格内容区域
  6. 保留区域.

仅在第1页上找到100字节的数据库文件头,该页始终是表b树页. 数据库文件中的所有其他b树页面都忽略了此100字节的标头.

保留区域是每页末尾(锁定页除外)上未使用空间的区域,扩展可用于保留每页信息. 保留区域的大小由数据库文件头中偏移20的一字节无符号整数确定. 保留区域的大小通常为零.

b-tree页面标头的叶子页面大小为8个字节,内部页面大小为12个字节. 页面标题中的所有多字节值均为big-endian. b树页头由以下字段组成:

B树页头格式
OffsetSizeDescription
01 偏移量0处的一字节标志指示b树页面类型.
  • 值为2(0x02)表示该页面是内部索引b树页面.
  • 值为5(0x05)表示该页面是内部表b树页面.
  • 值为10(0x0a)表示该页面是叶索引b树页面.
  • 值13(0x0d)表示该页面是叶表b树页面.
b树页面类型的任何其他值都是错误.
12 偏移量为1的两字节整数给出了页面上第一个空闲块的开始,如果没有空闲块,则为零.
32 偏移量3处的两字节整数给出页面上的单元格数.
52 偏移量5处的2字节整数表示单元内容区域的开始. 该整数的零值解释为65536.
71 偏移量7处的一字节整数给出了单元内容区域内的零碎可用字节数.
84 偏移量8处的四字节页码是最右边的指针. 此值仅出现在内部b树页面的页眉中,而在所有其他页面中均省略.

b树页面的单元指针数组紧随b树页面标题之后. 令K为btree上的像元数. 单元格指针数组由指向单元格内容的K个2字节整数偏移量组成. 单元格指针按键顺序排列,最左边的单元格(具有最小键的单元格)排在最前面,最右边的单元格(具有最大键数的单元格)排在最后.

单元格内容存储在b树页面的单元格内容区域中. SQLite努力将单元格尽可能地放置在b树页面的尽头,以便为单元格指针数组的未来增长留出空间. 最后一个单元格指针数组条目与第一个单元格的开头之间的区域是未分配的区域.

如果页面不包含任何单元格(这仅适用于不包含行的表的根页面),则单元格内容区域的偏移量将等于页面大小减去保留空间的字节数. 如果数据库使用65536字节的页面大小,且保留空间为零(保留空间的通常值),则空页面的单元格内容偏移量希望为65536.但是,该整数太大,无法存储在2字节无符号整数,因此在其位置使用0值.

freeblock是一种用于标识b树页内未分配空间的结构. Freeblocks是按链组织的. 一个自由块的前2个字节是一个大端整数,它是链中下一个自由块的b树页面中的偏移量;如果该自由块是链中的最后一个字节,则为零. 每个空闲块的第三个和第四个字节形成一个大端整数,它是空闲块的大小(以字节为单位),包括4字节的标头. 自由块始终按偏移量增加的顺序进行连接. b树页面标题的第二个字段是第一个空闲块的偏移量;如果页面上没有空闲块,则为零. 在结构良好的b树页面中,在第一个空闲块之前将始终至少有一个单元格.

一个freeblock至少需要4个字节的空间. 如果单元格内容区域中有1个,2个或3个未使用字节的隔离组,则这些字节构成一个片段. 所有片段中的字节总数存储在b树页标题的第五个字段中. 在格式良好的b树页面中,片段中的字节总数不得超过60.

b树页面上的可用空间总量由未分配区域的大小加上所有空闲块的总大小加上分段的可用字节数组成. SQLite可能会不时重组b树页面,以便没有空闲块或片段字节,所有未使用的字节都包含在未分配的空间区域中,并且所有单元格都紧紧地包装在页面末尾. 这称为对b树页面进行"碎片整理".

可变长度整数或" varint"是64位二进制补码整数的静态霍夫曼编码,对于较小的正值使用较少的空间. varint的长度在1到9个字节之间. varint包含零个或多个具有高位位的字节,后跟具有高位位清零的单个字节或九个字节(以较短者为准). 前八个字节中每个字节的低七位和第九个字节中的所有8位用于重构64位二进制补码整数. Varint是big-endian:从varint的前一个字节获取的位比从后一个字节获取的位更重要.

单元格的格式取决于该单元格出现在哪种b树页面上. 下表按外观顺序显示了各种b树页面类型的单元格元素.

Table B-Tree Leaf Cell (header 0x0d):

  • varint,是有效载荷的字节总数,包括任何溢出
  • varint是整数键,又名" rowid "
  • 有效载荷的初始部分不会溢出到溢出页面.
  • 溢出页列表的第一页的4字节大尾数整数页号-如果所有有效负载都适合b树页,则将其省略.

Table B-Tree Interior Cell (header 0x05):

  • 一个4字节的big-endian页号,它是左子指针.
  • varint是整数键

Index B-Tree Leaf Cell (header 0x0a):

  • varint,是密钥有效载荷的总字节数,包括所有溢出
  • 有效载荷的初始部分不会溢出到溢出页面.
  • 溢出页列表的第一页的4字节大尾数整数页号-如果所有有效负载都适合b树页,则将其省略.

Index B-Tree Interior Cell (header 0x02):

  • 一个4字节的big-endian页号,它是左子指针.
  • varint,是密钥有效载荷的总字节数,包括所有溢出
  • 有效载荷的初始部分不会溢出到溢出页面.
  • 溢出页列表的第一页的4字节大尾数整数页号-如果所有有效负载都适合b树页,则将其省略.

上面的信息可以重新铸成表格格式,如下所示:

B树单元格格式
Datatype 出现在... Description
桌叶(0x0d) 桌子内部(0x05) 索引叶(0x0a) 内部索引(0x02)
4字节整数     左孩子的页码
varint   有效载荷的字节数
varint     Rowid
字节数组   Payload
4字节整数   第一个溢出页的页码

溢出到溢出页面上的有效负载量还取决于页面类型. 对于以下计算,令U为数据库页面的可用大小,即总页面大小减去每个页面末尾的保留空间. 令P为有效载荷大小. 在下文中,符号X表示可以直接存储在b树页面上而不会溢出到溢出页面上的最大有效负载量,而符号M表示在允许溢出之前必须存储在btree页面上的最小有效负载量. .

Table B-Tree Leaf Cell:

令X为U-35. 如果有效载荷大小P小于或等于X,则整个有效载荷都存储在b树叶子页上. 令M为((U-12)* 32/255)-23,令K为M +((PM)%(U-4)). 如果P大于X,则如果K小于或等于X或M,则存储在表b树叶子页上的字节数为K. 叶子页上存储的字节数不得少于M.

Table B-Tree Interior Cell:

表b树的内部页没有有效负载,因此永远不会溢出任何有效负载.

Index B-Tree Leaf Or Interior Cell:

设X为((U-12)* 64/255)-23. 如果有效载荷大小P小于或等于X,则整个有效载荷都存储在b树页面上. 令M为((U-12)* 32/255)-23,令K为M +((PM)%(U-4)). 如果P大于X,则如果K小于或等于X或M,则存储在索引b树页上的字节数为K. 索引页上存储的字节数不得少于M.

这是相同计算的替代描述:

溢出阈值旨在为索引b树提供至少4的扇出,并确保b树页上有足够的有效负载,以便通常可以在不查询溢出页的情况下访问记录头. 事后看来,SQLite b树逻辑的设计者意识到可以简化这些阈值. 但是,如果不导致不兼容的文件格式,则无法更改计算. 即使有些复杂,当前的计算也可以正常工作.

1.7. Cell Payload Overflow Pages

当b树单元的有效负载对于b树页面而言太大时,多余的部分会溢出到溢出页面上. 溢出页面形成一个链接列表. 每个溢出页的前四个字节是一个大端整数,它是链中下一页的页号,对于链中的最后一页为零. 从第五个字节到最后一个可用字节用于保存溢出内容.

1.8. Pointer Map or Ptrmap Pages

指针映射或ptrmap页面是插入数据库中的额外页面,以使auto_vacuum增量 _vacuum模式的操作更加有效. 数据库中的其他页面类型通常具有从父级到子级的指针. 例如,内部b树页面包含指向其子b树页面的指针,并且溢出链具有从链中较早的链接到后来的链接的指针. 一个ptrmap页面包含从子级到父级的相反方向的链接信息.

Ptrmap页必须存在于数据库头中偏移量52处具有最大非零最大根b树页面值的任何数据库文件中. 如果最大的根b树页面值为零,则数据库不得包含ptrmap页面.

在具有ptrmap页的数据库中,第一个ptrmap页是第2页.一个ptrmap页由5字节条目的数组组成. 令J为适合页面可用空间的5字节条目数. (换句话说,J = U / 5.)第一个ptrmap页面将包含页面3至J + 2(包括3和2)的反向指针信息. 第二个指针映射页面将在页面J + 3上,而该ptrmap页面将提供页面J + 4到2 * J + 3(包括两端)的反向指针信息. 整个数据库文件依此类推.

在使用ptrmap页面的数据库中,上一段中由计算确定的位置处的所有页面必须是ptrmap页面,并且其他任何页面都不能是ptrmap页面. 除非字节锁定页恰好与ptrmap页位于同一页号上,否则在这种情况下ptrmap将移至下一页.

ptrmap页面上的每个5字节条目都提供有关紧随指针映射的页面之一的反向链接信息. 如果页面B是ptrmap页面,则指针映射上的第一个条目将提供有关页面B + 1的反向链接信息. 关于页面B + 2的信息由第二个条目提供. 依此类推.

每个5字节的ptrmap条目均包含一个字节的"页面类型"信息,后跟一个4字节的大尾数页码. 可以识别五种页面类型:

  1. A b-tree root page. The page number should be zero.
  2. 一个空闲列表页面. 页码应为零.
  3. 单元有效负载溢出链的第一页. 页码是b树页面,其中包含内容已溢出的单元格.
  4. 溢出链中除第一页以外的页面. 页码是溢出链的前一页.
  5. 非根b树页面. 页码是父b树页.

在任何包含ptrmap页的数据库文件中,所有b树根页必须位于任何非根b树页,单元有效载荷溢出页或空闲列表页之前. 此限制可确保在自动真空或增量真空期间永远不会移动根页面. 自动真空逻辑不知道如何更新sqlite_master表的root_page字段,因此有必要防止在自动真空过程中移动根页,以保持sqlite_master表的完整性. 根页面通过CREATE TABLE,CREATE INDEX,DROP TABLE和DROP INDEX操作移到数据库文件的开头.

2. Schema Layer

前面的文字描述了SQLite文件格式的低级方面. b树机制提供了访问大型数据集的强大而有效的方法. 本节将描述如何使用低级b树层来实现高级SQL功能.

2.1. Record Format

上面将表b树叶子页和索引b树页的键的数据表征为任意字节序列. 先前的讨论提到一个键小于另一个键,但是没有定义"小于"的含义. 本节将解决这些遗漏.

表b-树数据或索引b-树键的有效载荷始终为"记录格式". 记录格式定义了与表或索引中的列相对应的值序列. 记录格式指定列数,每一列的数据类型以及每一列的内容.

记录格式大量使用了上面定义的64位有符号整数的变长整数varint表示形式.

记录按顺序包含标题和正文. 标头以单个varint开头,它确定标头中的字节总数. varint值是标头的大小(以字节为单位),包括大小varint本身. 在大小varint之后是一个或多个其他varint,每列一个. 这些额外的varint称为"序列类型"数字,根据下表确定每个列的数据类型:

记录格式的序列类型代码
序列类型内容大小Meaning
00 值为NULL.
11 值是一个8位二进制补码整数.
22 值是一个大端16位二进制补码整数.
33 值是一个大端的24位二进制补码整数.
44 值是一个大端32位二进制补码整数.
56 值是一个大端48位二进制补码整数.
68 值是一个大端64位二进制补码整数.
78 值是big-endian IEEE 754-2008 64位浮点数.
80 值是整数0.(仅适用于架构格式 4和更高版本.)
90 值是整数1.(仅适用于架构格式 4和更高版本.)
10,11 variable 保留供内部使用. 这些串行类型代码将永远不会出现在格式正确的数据库文件中,但是它们可能会用于SQLite有时会为其自身使用而生成的临时和临时数据库文件中. 这些代码的含义可以从一个SQLite版本转移到另一个版本.
N≥12 and even (N-12)/2 值为BLOB,长度为(N-12)/ 2个字节.
N≥13且为奇数 (N-13)/2 值是文本编码的字符串,长度为(N-13)/ 2个字节. 不存储nul终止符.

标头大小的varint和串行类型的varint通常将由一个字节组成. 大字符串和BLOB的串行类型varint可能会扩展为两个或三个字节的varint,但这是例外,而不是规则. varint格式在编码记录头方面非常有效.

记录中每一列的值紧随标题之后. 对于串行类型0、8、9、12和13,该值的长度为零字节. 如果所有列均为这些类型,则记录的主体部分为空.

一条记录的值可能少于相应表中的列数. 例如,在ALTER TABLE ... ADD COLUMN SQL语句增加了表架构中的列数而不修改表中已有的行之后,可能会发生这种情况. 记录末尾的缺失值使用表模式中定义的对应列的默认值填充.

2.2. Record Sort Order

索引b树中的键顺序由键代表的记录的排序顺序确定. 逐列记录比较进度. 从左到右检查记录的列. 第一对不相等的列确定两个记录的相对顺序. 各个列的排序顺序如下:

  1. NULL值(序列类型0)首先排序.
  2. 数字值(1到9的串行类型)在NULL之后并按数字顺序排序.
  3. 文本值(奇数串行类型13及更大)按列整理函数确定的顺序在数字值之后排序 .
  4. BLOB值(甚至是串行类型12和更大的值)最后排列,并按memcmp()确定的顺序排序.

为了计算文本字段的顺序,每列的整理功能是必需的. SQLite定义了三个内置的整理功能:

BINARY The built-in BINARY collation compares strings byte by byte using the memcmp() function from the standard C library.
NOCASE NOCASE排序规则类似于BINARY,不同之处在于,在运行比较之前,将大写ASCII字符(" A"至" Z")折叠为它们的小写字母. 仅ASCII字符会被大小写折叠. NOCASE不会实现通用unicode不区分大小写的比较.
RTRIM RTRIM类似于BINARY,只是两个字符串末尾的多余空格不会更改结果. 换句话说,只要字符串的结尾处的空格数不同,它们就将相互比较.

可以使用sqlite3_create_collat​​ion()接口将其他特定于应用程序的整理功能添加到SQLite.

所有字符串的默认整理功能是BINARY. 可以使用列定义上的COLLATE子句在CREATE TABLE语句中指定表列的替代整理功能. 索引列时,默认情况下,为索引中的列使用在CREATE TABLE语句中指定的相同整理函数,尽管可以使用CREATE INDEX语句中的COLLATE子句将其覆盖.

2.3. Representation Of SQL Tables

数据库模式中的每个普通SQL表在磁盘上都由表b树表示. 表b树中的每个条目对应于SQL表的一行. SQL表的rowid是表b树中每个条目的64位带符号整数键.

The content of each SQL table row is stored in the database file by first combining the values in the various columns into a byte array in the record format, then storing that byte array as the payload in an entry in the table b-tree. The order of values in the record is the same as the order of columns in the SQL table definition. When an SQL table includes an INTEGER PRIMARY KEY column (which aliases the rowid) then that column appears in the record as a NULL value. SQLite will always use the table b-tree key rather than the NULL value when referencing the INTEGER PRIMARY KEY column.

如果列的亲和力为REAL,并且该列包含可以转换为整数而不会丢失信息的值(如果该值不包含小数部分,并且也不能太大以表示为整数),则该列可能是以整数形式存储在记录中. 从记录中提取值时,SQLite会将值转换回浮点.

2.4. Representation of WITHOUT ROWID Tables

如果在其CREATE TABLE语句的末尾使用" WITHOUT ROWID"子句创建了一个SQL表,则该表是一个WITHOUT ROWID表,并使用不同的磁盘表示形式. WITHOUT ROWID表使用索引b树而不是表b树进行存储. 在WITHOUT ROWID b树中,每个条目的键都是一条记录,该记录由PRIMARY KEY的列以及表的所有其余列组成. 主键列按它们在PRIMARY KEY子句中声明的顺序显示,其余列按它们在CREATE TABLE语句中出现的顺序显示.

因此,WITHOUT ROWID表的内容编码与普通rowid表的内容编码相同,不同之处在于,重新排列了列的顺序,使得PRIMARY KEY列排在最前面,并且该内容用作索引b树,而不是表b树中的数据. 具有REAL亲和力的列的特殊编码规则适用于WITHOUT ROWID表,与使用rowid表相同.

2.4.1. Suppression of redundant columns in the PRIMARY KEY of WITHOUT ROWID tables

如果WITHOUT ROWID表的PRIMARY KEY多次使用具有相同整理顺序的相同列,则将忽略PRIMARY KEY定义中该列的第二次及以后出现. 例如,以下CREATE TABLE语句均指定相同的表,该表在磁盘上具有完全相同的表示形式:

CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,c)) WITHOUT ROWID);
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,c,a,c)) WITHOUT ROWID);
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,A,a,C)) WITHOUT ROWID);
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,a,a,a,c)) WITHOUT ROWID);

当然,上面的第一个示例是表的首选定义. 所有这些示例都创建了一个WITHOUT ROWID表,该表具有两个PRIMARY KEY列,分别为" a"和" c",然后是两个数据列" b"和" d",也都以此顺序.

2.5. Representation Of SQL Indices

每个SQL索引,无论是通过CREATE INDEX语句显式声明还是由UNIQUE或PRIMARY KEY约束隐式表示,都对应于数据库文件中的索引b树. 索引b树中的每个条目对应于关联的SQL表中的一行. 索引b树的键是一条记录,该记录由要索引的列组成,后跟相应表行的键. 对于普通表,行键是rowid ,对于WITHOUT ROWID表,行键是PRIMARY KEY. 由于表中的每一行都有唯一的行键,因此索引中的所有键都是唯一的.

在普通索引中,表中的行与与该表关联的每个索引中的条目之间存在一对一的映射. 但是,在部分索引中 ,索引b树仅包含与CREATE INDEX语句上的WHERE子句表达式为true的表行相对应的条目. 索引树b和表b树中的相应行共享相同的rowid或主键值,并且对于所有索引列都包含相同的值.

2.5.1. Suppression of redundant columns in WITHOUT ROWID secondary indexes

在WITHOUT ROWID表的索引中,如果PRIMARY KEY的列也是索引中的列,并且具有匹配的排序顺序,则在索引记录末尾的表键后缀中不会重复索引的列. 例如,考虑以下SQL:

CREATE TABLE ex25(a,b,c,d,e,PRIMARY KEY(d,c,a)) WITHOUT rowid;
CREATE INDEX ex25ce ON ex25(c,e);
CREATE INDEX ex25acde ON ex25(a,c,d,e);
CREATE INDEX ex25ae ON ex25(a COLLATE nocase,e);

ex25ce索引中的每一行都是包含以下列的记录:c,e,d,a. 前两列是要索引的列c和e. 其余的列是相应表行的主键. 通常,主键将是d,c和a列,但是由于c列已经出现在索引的前面,因此在键后缀中将其省略.

在被索引的列覆盖PRIMARY KEY的所有列的极端情况下,索引将仅由被索引的列组成. 上面的ex25acde示例演示了这一点. ex25acde索引中的每个条目仅按顺序包含a,c,d和e列.

ex25ae中的每一行都包含五列:a,e,d,c,a. 重复" a"列,因为第一次出现的" a"具有整理功能" nocase",第二次出现的整理顺序为" binary". 如果不重复" a"列,并且该表包含两个或多个具有相同" e"值的条目,并且其中" a"仅在情况不同的情况下,则所有这些表条目将对应于索引中的单个条目,这将破坏表与索引之间的一一对应关系.

The suppression of redundant columns in the key suffix of an index entry only occurs in WITHOUT ROWID tables. In an ordinary rowid table, the index entry always ends with the rowid even if the INTEGER PRIMARY KEY column is one of the columns being indexed.

2.6. Storage Of The SQL Database Schema

数据库文件的第1页是表b-树的根页,该表b-树包含一个特殊的表,该表名为" sqlite_master"(在TEMP数据库的情况下为" sqlite_temp_master"),该表存储了完整的数据库模式. sqlite_master表的结构就像是使用以下SQL创建的:

CREATE TABLE sqlite_master(
  type text,
  name text,
  tbl_name text,
  rootpage integer,
  sql text
);

sqlite_master表针对数据库架构中的每个表,索引,视图和触发器(统称为"对象")包含一行,但sqlite_master表本身没有条目. sqlite_master表除了应用程序和程序员定义的对象外,还包含内部模式对象的条目.

sqlite_master.type列将是以下文本字符串之一:" table"," index"," view"或" trigger"(根据定义的对象类型). 'table'字符串用于普通虚拟表 .

sqlite_master.name列将保存对象的名称. 表上的UNIQUEPRIMARY KEY约束会导致SQLite创建名称形式为" sqlite_autoindex_TABLE_N"的内部索引 ,其中TABLE替换为包含约束的表的名称,N是一个以1开头并随每个约束加1的整数.见表定义. 在WITHOUT ROWID表中,没有PRIMARY KEY的sqlite_master条目,但是为PRIMARY KEY保留了" sqlite_autoindex_TABLE_N"名称,就好像sqlite_master条目确实存在一样. 这将影响后续UNIQUE约束的编号. 永远不会在rowid表或WITHOUT ROWID表中为INTEGER PRIMARY KEY分配" sqlite_autoindex_TABLE_N"名称.

sqlite_master.tbl_name列包含与该对象关联的表或视图的名称. 对于表或视图,tbl_name列是name列的副本. 对于索引,tbl_name是被索引表的名称. 对于触发器,tbl_name列存储导致触发器触发的表或视图的名称.

sqlite_master.rootpage列存储表和索引的根b树页面的页码. 对于定义视图,触发器和虚拟表的行,根页列为0或NULL.

sqlite_master.sql列存储描述对象的SQL文本. 该SQL文本是CREATE TABLECREATE VIRTUAL TABLECREATE INDEXCREATE VIEWCREATE TRIGGER语句,如果针对数据库文件进行评估(如果它是数据库连接的主数据库),则会重新创建该对象. 文本通常是用于创建对象的原始语句的副本,但是应用了规范化,因此文本符合以下规则:

sqlite_master.sql列中的文本是创建对象的原始CREATE语句文本的副本,但如上所述以及通过后续ALTER TABLE语句进行了修改的规范除外. 对于由UNIQUEPRIMARY KEY约束自动创建的内部索引 ,sqlite_master.sql为NULL.

2.6.1. Internal Schema Objects

that are created by SQLite for its own internal use. 除了由应用程序和/或开发人员使用CREATE语句SQL创建的表,索引,视图和触发器之外,sqlite_master表可能包含零个或多个由SQLite创建的条目,这些条目供其自身内部使用. 内部架构对象的名称始终以" sqlite_"开头,而名称以" sqlite_"开头的任何表,索引,视图或触发器都是内部架构对象. SQLite禁止应用程序创建名称以" sqlite_"开头的对象.

SQLite使用的内部架构对象可能包括以下内容:

新的内部模式对象名称(始终以" sqlite_"开头)可能会在将来的版本中添加到SQLite文件格式中.

2.6.2. The sqlite_sequence table

sqlite_sequence表是用于帮助实现AUTOINCREMENT的内部表. 每当创建具有AUTOINCREMENT整数主键的任何普通表时,都会自动创建sqlite_sequence表. 创建后,sqlite_sequence表将永远存在于sqlite_master表中. 它不能被丢弃. sqlite_sequence表的架构为:

CREATE TABLE sqlite_sequence(name,seq);

对于使用AUTOINCREMENT的每个普通表,在sqlite_sequence表中只有一行. 该表的名称(显示在sqlite_master.name中)在sqlite_sequence.main字段中,并且有史以来最大的INTEGER PRIMARY KEY插入该表在sqlite_sequence.seq字段中. 确保为AUTOINCREMENT表自动生成的新的整数主键大于该表的sqlite_sequence.seq字段. 如果AUTOINCREMENT表的sqlite_sequence.seq字段已经是最大整数值(9223372036854775807),则尝试使用自动生成的整数主变量向该表添加新行将失败,并显示SQLITE_FULL错误. 将新条目插入到AUTOINCREMENT表中时,如果需要,可自动更新sqlite_sequence.seq字段. 删除表时,将自动删除AUTOINCREMENT表的sqlite_sequence行. 如果在更新AUTOINCREMENT表时不存在AUTOINCREMENT表的sqlite_sequence行,那么将创建一个新的sqlite_sequence行. 如果将AUTOINCREMENT表的sqlite_sequence.seq值手动设置为整数以外的其他值,并且随后尝试插入或更新AUTOINCREMENT表,则该行为未定义.

允许应用程序代码修改sqlite_sequence表,添加新行,删除行或修改现有行. 但是,如果尚不存在sqlite_sequence表,则应用程序代码无法创建该表. 应用程序代码可以删除sqlite_sequence表中的所有条目,但是应用程序代码不能删除sqlite_sequence表.

2.6.3. The sqlite_stat1 table

sqlite_stat1是由ANALYZE命令创建的内部表,用于保存有关表和索引的补充信息,查询计划程序可以使用这些信息来帮助其查找执行查询的更好方法. 应用程序可以更新,删除,插入或删除sqlite_stat1表,但不能创建或更改sqlite_stat1表. sqlite_stat1表的架构如下:

CREATE TABLE sqlite_stat1(tbl,idx,stat);

通常每个索引有一行,该索引由sqlite_stat1.idx列中的名称标识. sqlite_stat1.tbl列是索引所属的表的名称. 在每个此类行中,sqlite_stat.stat列将是一个字符串,其中包含一个整数列表,后跟零个或多个参数. 此列表中的第一个整数是索引中的大约行数. (除部分索引外, 索引中的行数与表中的行数相同.)第二个整数是索引中第一行中具有相同值的近似行数. 第三个整数是索引中前两列具有相同值的行数. 第N个整数(对于N> 1)是索引中对前N-1列具有相同值的估计行平均数. 对于K列索引,stat列中将有K + 1个整数. 如果索引是唯一的,则最后一个整数将为1.

stat列中的整数列表可以可选地后面跟参数,每个参数都是一个非空格字符序列. 所有参数前面都有一个空格. 无法识别的参数将被静默忽略.

如果存在"无序"参数,则查询计划者将假定索引是无序的,并且不会将索引用于范围查询或排序.

" sz = NNN"自变量(其中NNN表示1或多个数字的序列)表示表或索引的所有记录的平均行大小为每行NNN字节. SQLite查询计划程序可能会使用" sz = NNN"令牌提供的估计行大小信息来帮助它选择需要较少磁盘I / O的较小表和索引.

索引的sqlite_stat1.stat字段上存在" noskipscan"令牌,这会阻止该索引与跳过扫描优化配合使用 .

New text tokens may be added to the end of the stat column in future enhancements to SQLite. For compatibility, unrecognized tokens at the end of the stat column are silently ignored.

如果sqlite_stat1.idx列为NULL,则sqlite_stat1.stat列包含单个整数,该整数是由sqlite_stat1.tbl标识的表中的大约行数. 如果sqlite_stat1.idx列与sqlite_stat1.tbl列相同,则该表是WITHOUT ROWID表,而sqlite_stat1.stat字段包含有关实现WITHOUT ROWID表的索引btree的信息.

2.6.4. The sqlite_stat2 table

sqlite_stat2仅在使用SQLITE_ENABLE_STAT2编译SQLite且SQLite版本号在3.6.18(2009-09-11)和3.7.8(2011-09-19)之间时创建并使用. 在3.6.18之前或3.7.8之后,任何版本的SQLite均无法读取或写入sqlite_stat2表. sqlite_stat2表包含有关索引内键的分布的其他信息. sqlite_stat2表的架构如下:

CREATE TABLE sqlite_stat2(tbl,idx,sampleno,sample);

sqlite_stat2表的每一行中的sqlite_stat2.idx列和sqlite_stat2.tbl列标识该行描述的索引. sqlite_stat2表中通常每个索引有10行.

sqlite_stat2.sampleno在0到9之间(含0和9)的索引的sqlite_stat2条目是索引中最左键值的采样,这些采样是在沿索引均匀间隔的点处获取的. 令C为索引中的行数. 然后采样行由

行数=(i * C * 2 + C)/ 20

上一个表达式中的变量i在0到9之间变化.从概念上讲,索引空间分为10个统一的存储桶,样本位于每个存储桶的中间行.

sqlite_stat2的格式记录在此处,以供旧参考. SQLite的最新版本不再支持sqlite_stat2,而sqlite_stat2表(如果存在)将被忽略.

2.6.5. The sqlite_stat3 table

只有在使用SQLITE_ENABLE_STAT3SQLITE_ENABLE_STAT4编译SQLite且SQLite版本号为3.7.9(2011-11-01)或更高版本时,才使用sqlite_stat3 . 在3.7.9之前的任何版本的SQLite都无法读写sqlite_stat3表. 如果使用了SQLITE_ENABLE_STAT4编译时选项,并且SQLite版本号是3.8.1(2013-10-17)或更高版本,则sqlite_stat3可能被读取但未被写入. sqlite_stat3表包含有关索引内键的分布的其他信息,查询计划人员可以使用该信息来设计更好,更快的查询算法. sqlite_stat3表的架构如下:

CREATE TABLE sqlite_stat3(tbl,idx,nEq,nLt,nDLt,sample);

每个索引在sqlite_stat3表中通常有多个条目. sqlite_stat3.sample列保存由sqlite_stat3.idx和sqlite_stat3.tbl标识的索引的最左侧字段的值. sqlite_stat3.nEq列保留索引中最左列与样本完全匹配的条目的大约数量. sqlite_stat3.nLt保留索引中最左列小于样本的条目的近似数目. sqlite_stat3.nDLt列包含索引中小于样本的最左边不同条目的大约数量.

每个索引可以有任意数量的sqlite_stat3条目. ANALYZE命令通常会生成sqlite_stat3表,该表包含10至40个样本,这些样本分布在整个键空间中,并且具有较大的nEq值.

在格式良好的sqlite_stat3表中,任何单个索引的样本必须按照它们在索引中出现的顺序出现. 换句话说,如果索引b树中具有最左列S1的条目早于具有最左列S2的条目,则在sqlite_stat3表中,样本S1的行标识必须小于样本S2.

2.6.6. The sqlite_stat4 table

sqlite_stat4仅在使用SQLITE_ENABLE_STAT4编译SQLite且SQLite版本号为3.8.1(2013-10-17)或更高版本时创建并使用. 在3.8.1之前的任何版本的SQLite都无法读写sqlite_stat4表. sqlite_stat4表包含有关索引中的键分布或WITHOUT ROWID表的主键中的键分布的其他信息. 查询计划者有时可以使用sqlite_stat4表中的其他信息来设计更好,更快的查询算法. sqlite_stat4表的架构如下:

CREATE TABLE sqlite_stat4(tbl,idx,nEq,nLt,nDLt,sample);

对于每个可用统计数据的索引,sqlite_stat4表中通常有10到40个条目,但是这些限制不是硬性限制. sqlite_stat4表中各列的含义如下:

tbl: sqlite_stat4.tbl列包含拥有该行描述的索引的表的名称
idx: sqlite_stat4.idx列保存该行描述的索引的名称,或者对于WITHOUT ROWID表而言,在sqlite_stat4条目的情况下,保留表本身的名称.
sample: sqlite_stat4.sample列包含记录格式的BLOB,该BLOB编码索引列,后跟rowid表的rowid或WITHOUT ROWID表的主键的列. WITHOUT ROWID表本身的sqlite_stat4.sample BLOB仅包含主键的列. 让sqlite_stat4.sample Blob编码的列数为N.对于普通rowid表上的索引,N将比索引的列数多一. 对于WITHOUT ROWID表上的索引,N将是被索引的列数加上主键中的列数. 对于WITHOUT ROWID表,N将为主键中的列数.
nEq: sqlite_stat4.nEq列包含N个整数的列表,其中第K个整数是索引中最左边的K列与样本的最左边K列完全匹配的条目的近似数目.
nLt: sqlite_stat4.nLt列包含N个整数的列表,其中第K个整数是索引中其K个最左列共同小于样本的K个最左列的条目的近似数目.
nDLt: sqlite_stat4.nDLt列包含N个整数的列表,其中第K个整数是索引中在前K列中不同的条目的近似数目,而最左边的K列的总和少于最左边的样本的K列.

sqlite_stat4是sqlite_stat3表的概括. sqlite_stat3表提供有关索引最左列的信息,而sqlite_stat4表提供有关索引所有列的信息.

每个索引可以有任意数量的sqlite_stat4条目. ANALYZE命令通常会生成sqlite_stat4表,该表包含10至40个样本,这些样本分布在整个键空间中,并且具有较大的nEq值.

在格式良好的sqlite_stat4表中,任何单个索引的样本必须按照它们在索引中出现的顺序出现. 换句话说,如果索引b树中的条目S1比条目S2早,则在sqlite_stat4表中,样本S1的行ID必须小于样本S2.

3. The Rollback Journal

回滚日志是与每个SQLite数据库文件关联的文件,其中包含用于在事务过程中将数据库文件还原到其初始状态的信息. 回滚日志文件始终与数据库文件位于同一目录中,并且与数据库文件具有相同的名称,但是附加了字符串" -journal ". 与给定数据库只能有一个回滚日志,因此一次只能对一个数据库打开一个写事务.

If a transaction is aborted due to an application crash, an operating system crash, or a hardware power failure or crash, then the database may be left in an inconsistent state. The next time SQLite attempts to open the database file, the presence of the rollback journal file will be detected and the journal will be automatically played back to restore the database to its state at the start of the incomplete transaction.

回滚日志只有存在并且包含有效的标头,才被认为是有效的. 因此,可以通过以下三种方式之一提交事务:

  1. 回滚日志文件可以删除,
  2. 回滚日志文件可以被截断为零长度,或者
  3. 可以使用无效的标题文本(例如,全零)覆盖回滚日志的标题.

这三种提交事务的方式分别对应于journal_mode pragma的DELETE,TRUNCATE和PERSIST设置.

有效的回滚日志以以下格式的标题开头:

Rollback Journal Header Format
OffsetSizeDescription
0 8 标头字符串:0xd9、0xd5、0x05、0xf9、0x20、0xa1、0x63、0xd7
8 4 "页数"-日记的下一部分中的页数,或-1表示文件末尾的所有内容
12 4 校验和的随机随机数
16 4 数据库的初始大小(以页为单位)
20 4 写入此日志的进程假定的磁盘扇区大小.
24 4 此日志中的页面大小.

回滚日志标头用零填充到单个扇区的大小(由偏移量20处的扇区大小整数定义). 标头本身位于一个扇区中,因此,如果在写入扇区时发生断电,则标头之后的信息将(希望)不受损坏.

标头和零填充之后是零个或多个页面记录. 每个页面记录都会存储数据库文件更改之前页面内容的副本. 同一页在一个回滚日志中最多只能出现一次. 要回滚一个不完整的事务,一个过程只需从头到尾读取回滚日志,并将日志中找到的页面写回到适当位置的数据库文件中.

令数据库页面大小(日志标题中偏移量24处的整数的值)为N.然后,页面记录的格式如下:

回滚日志页面记录格式
OffsetSizeDescription
0 4 数据库文件中的页码
4 N 交易开始之前页面的原始内容
N+4 4 Checksum

校验和是一个无符号的32位整数,其计算如下:

  1. 初始化校验和为日记头中偏移量为12的校验和现时值.
  2. 将索引X初始化为N-200(其中N是数据库页面的大小,以字节为单位).
  3. 将偏移量X处的字节解释为8位无符号整数,并将该整数的值添加到校验和中.
  4. 从X减去200.
  5. 如果X大于或等于零,请返回步骤3.

校验和值用于防止电源故障后日记页面记录的不完整写入. 每次启动事务时,都会使用不同的随机随机数,以最大程度地降低未写扇区可能偶然包含来自先前日记一部分的同一页面中数据的风险. 通过更改每个事务的随机数,磁盘上的陈旧数据仍将生成不正确的校验和,并且很有可能被检测到. 出于性能原因,校验和仅使用数据记录中的32位字的稀疏样本-SQLite 3.0.0计划阶段的设计研究显示,校验和对整个页面的性能产生了重大影响.

设日记帐标题中偏移量8的页计数值为M.如果M大于零,则在M页记录之后,日记文件可以零填充到扇区大小的下一个倍数,并且可以插入另一个日记帐标题. 同一日志中的所有日志标题必须包含相同的数据库页面大小和扇区大小.

如果初始日记帐标题中的M为-1,则通过计算将在日记帐文件其余部分的可用空间中容纳多少页记录来计算随后的页记录数.

4. The Write-Ahead Log

版本3.7.0 (2010-07-21)开始,SQLite支持一种称为" 预写日志 "或" WAL "的新事务控制机制. 当数据库处于WAL模式时,与该数据库的所有连接都必须使用WAL. 特定的数据库将使用回滚日志或WAL,但不会同时使用两者. WAL始终与数据库文件位于同一目录中,并且与数据库文件具有相同的名称,但是附加了字符串" -wal ".

4.1. WAL File Format

WAL文件包含一个头,后跟零个或多个"帧". 每个框架记录数据库文件中单个页面的修订内容. 通过将帧写入WAL记录对数据库的所有更改. 写入包含提交标记的框架时,事务将提交. 一个WAL可以并且通常确实记录多个事务. 周期性地,WAL的内容通过称为"检查点"的操作被传输回数据库文件.

一个WAL文件可以重复使用多次. 换句话说,WAL可以先填充框架,然后再进行检查点设置,然后新框架可以覆盖旧框架. WAL总是从开始到结束不断发展. 附加到每个帧的校验和和计数器用于确定WAL中的哪些帧有效,哪些是先前检查点的剩余帧.

WAL标头的大小为32个字节,由以下八个大端32位无符号整数值组成:

WAL标头格式
OffsetSizeDescription
04 幻数. 0x377f0682或0x377f0683
44 文件格式版本. 目前为3007000.
84 数据库页面大小. 示例:1024
124 检查点序列号
164 Salt-1:随每个检查点递增的随机整数
204 Salt-2:每个检查点的随机数不同
244 Checksum-1:标头的前24个字节的校验和的第一部分
284 Checksum-2:标头的前24个字节的校验和的第二部分

紧随沃尔标头之后是零个或多个帧. bytes of page data. 每个帧包含一个24字节的帧头,后跟页面数据的字节. 帧头是六个大端32位无符号整数值,如下所示:

WAL帧头格式
OffsetSizeDescription
04 页码
44 对于提交记录,提交后数据库文件的大小(以页为单位). 对于所有其他记录,为零.
84 从WAL标头复制的Salt-1
124 从WAL标头复制的Salt-2
164 校验和-1:累积校验和直到整个页面
204 Checksum-2:累积校验和的后一半.

当且仅当满足以下条件时,才认为该帧有效:

  1. 帧标题中的salt-1和salt-2值与wal标题中的salt值匹配

  2. 帧头的最后8个字节中的校验和值与WAL头的前24个字节和前8个字节以及直到当前帧(包括当前帧)的所有帧的内容连续计算出的校验和完全匹配.

4.2. Checksum Algorithm

通过将输入解释为偶数个无符号32位整数(x(0)到x(N))来计算校验和. 如果WAL标头的前4个字节中的幻数为0x377f0683,则32位整数为big-endian;如果幻数为0x377f0682,则32位整数为little-endian. 无论将哪个字节顺序用于计算校验和,校验和值始终以大端格式存储在帧头中.

校验和算法仅适用于长度为8个字节的倍数的内容. 换句话说,如果输入是x(0)到x(N),则N必须是奇数. 校验和算法如下:

 
s0 = s1 = 0
for i from 0 to n-1 step 2:
   s0 += x(i) + s1;
   s1 += x(i+1) + s0;
endfor
# result in s0 and s1

输出s0和s1都是使用相反顺序的斐波那契权重的加权校验和. (最大斐波那契权重出现在要求和的序列的第一个元素上.)s1值覆盖该序列的所有32位整数项,而s0省略最后一项.

4.3. Checkpoint Algorithm

检查点上 ,首先使用VFS的xSync方法将WAL刷新到持久性存储中. 然后,将WAL的有效内容传输到数据库文件中. 最后,使用另一个xSync方法调用将数据库刷新到持久性存储. xSync操作充当写障碍-在xSync开始后启动的所有写操作之前,必须先完成在xSync之前启动的所有写操作.

检查点无需运行即可完成. 可能某些读者仍在使用旧的事务处理数据库文件中包含的数据. 在这种情况下,将用于较新交易的内容从WAL文件传输到数据库中,会从仍使用较旧交易的阅读器下方删除内容. 为避免这种情况,仅当所有阅读器都在使用WAL中的最后一个事务时,检查点才运行完成.

4.4. WAL Reset

在完成一个完整的检查点之后,如果使用WAL的事务中没有其他连接,则后续的写事务可以从头开始覆盖WAL文件. 这称为"重置WAL". 在第一个新的写事务开始时,WAL标头的salt-1值增加,并且salt-2值被随机化. 盐的这些更改会使WAL中已被检查点但尚未覆盖的旧帧失效,并防止它们再次被检查点.

可以选择将WAL文件在重置时截断,但不是必须的. 如果不截断WAL,性能通常会好一些,因为文件系统覆盖现有文件的速度通常比增长文件的速度快.

4.5. Reader Algorithm

要从数据库中读取页面(称为页面编号P),阅读器首先检查WAL以查看其是否包含页面P.如果是,则为页面P的最后一个有效实例,后跟一个提交框架或提交框架本身成为读取的值. 如果WAL不包含有效的页面P副本,这些副本是提交帧或后跟提交帧,则从数据库文件中读取页面P.

为了开始读取事务,读取器将WAL中的值帧数记录为" mxFrame". ( 更多详细信息 )阅读器将此记录的mxFrame值用于所有后续读取操作. 可以将新事务附加到WAL,但是只要读者使用其原始mxFrame值并忽略随后附加的内容,则读者将从单个时间点看到数据库的一致快照. 此技术允许多个并发阅读器同时查看数据库内容的不同版本.

上几段中的读取器算法可以正常工作,但是由于P页的帧可以出现在WAL中的任何位置,因此读取器必须扫描整个WAL寻找P页的帧. 如果WAL很大(通常为几兆字节),则扫描速度可能会很慢,并且读取性能会受到影响. 为克服此问题,维护了称为wal-index的单独数据结构,以加快对特定页面的框架的搜索.

4.6. WAL-Index Format

从概念上讲,wal-index是共享内存,尽管当前的VFS实现使用内存映射文件来实现操作系统的可移植性. 内存映射文件与数据库位于同一目录中,并且与数据库名称相同,后缀为" -shm ". 因为wal-index是共享内存,所以当客户端位于不同的计算机上时,SQLite不支持网络文件系统上的journal_mode = WAL ,因为数据库的所有客户端必须能够共享相同的内存.

wal-index的目的是快速回答这个问题:

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

上一段中的值是在第4.4节中定义的" mxFrame"值,该值在事务开始时读取,并且定义了读者将使用的来自WAL的最大帧.

wal-index是瞬态的. 崩溃之后,将从原始WAL文件中重建wal-index. 当与VFS的最后一个连接关闭时,要求VFS截断wal-index的标头或将其清零. 由于wal-index是瞬态的,因此可以使用特定于体系结构的格式. 它不必是跨平台的. 因此,与将所有值存储为big endian的数据库和WAL文件格式不同,wal-index以主机的本机字节顺序存储多字节值.

该文档涉及数据库文件的持久状态,并且由于wal-index是一个临时结构,因此在此将不提供有关wal-index格式的更多信息. 关于wal-index格式的其他详细信息包含在单独的WAL-index文件格式文档中.

by  ICOPY.SITE