小文件分析 - (一)
主要参考:
- 1、《SQLite数据库文件格式全面分析.doc 》,链接不详,只是能再网上各个文库上找到此文档。
- 2、官网文档 Database File Format
前提准备
准备好文件
在win10环境下
PS G:\code-2\sqlite3> ./sqlite3 small.db
SQLite version 3.28.0 2019-04-16 19:49:53
Enter ".help" for usage hints.
sqlite> CREATE TABLE department(id int, dept char(30), emp_id int);
sqlite> insert into department values(1, "test", -1);
sqlite> insert into department values(2, "test", 1);
sqlite> .qu
准备一些小工具
从sqlite官网下载的源码中的tool
目录中有一些十分有用的小工具,例如showdb
、showwal
等,它们都是单个源文件组成,结合官网提供的sqlite-amalgamation-xxx.zip
提供的sqlite3.h
和sqlite3.c
就可以编译出来。例如showdb
是由showdb.c
编译出来的:gcc -g -O0 -o showdb showdb.c sqlite3.c
,调试和编译优化选项看个人喜欢,可加可不加。
最最基础的概念
首先要明白三点:
- sqlite3数据库是由单个文件组成的,而这个文件被称为主数据库文件
main database file
。这个文件又是由多个页page
组成,页按顺序编号,从1开始。sqlite上层的b树最小组成单位就是页,至于如何存储,如何查找等我写完就差不多弄清楚了。还有页的分类都是后话,所有底层设计都是为了上层服务的,个人理解的时候要会把握关键的思路,再慢慢理顺细节。 - 存储在文件中的数据格式全部都是大端格式,为什么是大端呢?不清楚,就像网络字节序也是用大端存储,不过大端存储在调式的时候有个明显的优势就是符合人类阅读习惯。
-
sqlite_schema
是一个特殊的表(别名有很多,例如sqlite_master),它存放着其余表对应所在的根页号,具体定义如下:
CREATE TABLE sqlite_schema(
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
开始分析这个小文件
文件头
主数据库文件的前100个字节即是文件头。格式官网也给出了:
Offset | Size | Description |
---|---|---|
0 | 16 | The header string: "SQLite format 3\000" |
16 | 2 | The database page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536. |
18 | 1 | File format write version. 1 for legacy; 2 for WAL. |
19 | 1 | File format read version. 1 for legacy; 2 for WAL. |
20 | 1 | Bytes of unused "reserved" space at the end of each page. Usually 0. |
21 | 1 | Maximum embedded payload fraction. Must be 64. |
22 | 1 | Minimum embedded payload fraction. Must be 32. |
23 | 1 | Leaf payload fraction. Must be 32. |
24 | 4 | File change counter. |
28 | 4 | Size of the database file in pages. The "in-header database size". |
32 | 4 | Page number of the first freelist trunk page. |
36 | 4 | Total number of freelist pages. |
40 | 4 | The schema cookie. |
44 | 4 | The schema format number. Supported schema formats are 1, 2, 3, and 4. |
48 | 4 | Default page cache size. |
52 | 4 | The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. |
56 | 4 | The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. |
60 | 4 | The "user version" as read and set by the user_version pragma. |
64 | 4 | True (non-zero) for incremental-vacuum mode. False (zero) otherwise. |
68 | 4 | The "Application ID" set by PRAGMA application_id. |
72 | 20 | Reserved for expansion. Must be zero. |
92 | 4 | The version-valid-for number. |
96 | 4 | SQLITE_VERSION_NUMBER |
对应的小文件分析可以用showdb可知:
PS G:\code-2\sqlite3> ./showdb small.db dbheader
Pagesize: 4096
Available pages: 1..2
000: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
010: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 02 .....@ ........
020: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
030: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
060: 00 2e 34 20 00 ..4 .
Decoded:
010: 10 00 4096 Database page size
012: 01 1 File format write version
013: 01 1 File format read version
014: 00 0 Reserved space at end of page
018: 00 00 00 03 3 File change counter
01c: 00 00 00 02 2 Size of database in pages
020: 00 00 00 00 0 Page number of first freelist page
024: 00 00 00 00 0 Number of freelist pages
028: 00 00 00 01 1 Schema cookie
02c: 00 00 00 04 4 Schema format version
030: 00 00 00 00 0 Default page cache size
034: 00 00 00 00 0 Largest auto-vac root page
038: 00 00 00 01 1 Text encoding
03c: 00 00 00 00 0 User version
040: 00 00 00 00 0 Incremental-vacuum mode
044: 00 00 00 00 0 Application ID
048: 00 00 00 00 0 meta[8]
04c: 00 00 00 00 0 meta[9]
050: 00 00 00 00 0 meta[10]
054: 00 00 00 00 0 meta[11]
058: 00 00 00 00 0 meta[12]
05c: 00 00 00 03 3 Change counter for version number
060: 00 2e 34 20 3028000 SQLite version number
页大小 (Database page size)
在0x10偏移处,规定了页的大小,取值必须是2的幂次方,范围为[512, 65536]
,这里有点小细节可以参考官网文档可知。
文件格式版本(File format write/read version)
现在仅存在1和2两种,分别代表Hot journal
和WAL journal
。小文件分析时候采用的是前者。
页的保留空间大小 (Reserved space at end of page)
保留空间是在页的尾部开始,其的用途例如在加密插件启用的时候,存储nonce
值或者页面的校验和。一般来说这个字段一般为0。
Payload Fraction
0x21-23是连续的页内每个单元占用的百分比,以255为分母,这三个值64\32\32分别做分子,可得到对应的百分比25%,12.5%,12.5%。前两个是给index使用的,分别表示最大和最小值,后一个是给table b-tree leaf page的最小值,其最大值是是pagesize - 35
文件修改次数计数器(File change counter)
统计每一次修改数据库文件,到目前为止,我们先是创建表一次,两次独立(相当于两次事务)的插入操作,所以总共修改了文件3次。
当前数据库页的数目(Size of database in pages)
字面意思,不过历史版本上对其处理还是有不一样的,这个值会需要进一步的和数据库文件大小相比较,如果不一致则采用具体的数据库文件大小。
空闲页
这里开始涉及到页的分类了,先简单的理解一下空闲页。它的存在是因为数据库在进行删除操作的时候,导致整一页的数据都被清除掉了,此时就要把该页添加入空闲页链表。
这个链表在文件中的存储方式,是隐式链表,也就是页头会存放着指向下一个空闲页的下标。而第一个空闲页则由主数据库文件的文件头给出。
回到我们的小文件分析,此时我们的数据库没有多余的页,所以Page number of first freelist page == 0
,0可以理解为c语言中的NULL
,并且空闲页数目也等于0
Schema cookie
这里的Schema
,中文翻译怎么都不太恰当。它是整个数据库所有表的总和,而这cookie则是一个整数,每次Schema
发生改动(也就是发生表的增删改操作)就自增。这里一个相关的使用场景就是调用sqlite3_step
的时候会去检查schema
是否发生变化。
Schema format number
它代表着schema
不同历史时期的功能支持程度,可以见官网描述。这里直接摘抄原文,现在默认是用版本4。
- Format 1 is understood by all versions of SQLite back to version 3.0.0 (2004-06-18).
- Format 2 adds the ability of rows within the same table to have a varying number of columns, in order to support the ALTER TABLE ... ADD COLUMN functionality. Support for reading and writing format 2 was added in SQLite version 3.1.3 on 2005-02-20.
- Format 3 adds the ability of extra columns added by ALTER TABLE ... ADD COLUMN to have non-NULL default values. This capability was added in SQLite version 3.1.4 on 2005-03-11.
- Format 4 causes SQLite to respect the DESC keyword on index declarations. (The DESC keyword is ignored in indexes for formats 1, 2, and 3.) Format 4 also adds two new boolean record type values (serial types 8 and 9). Support for format 4 was added in SQLite 3.3.0 on 2006-01-10.
Default page cache size
默认的页缓存大小,只是一个建议,实际上由程序自己判断
Incremental vacuum settings
Largest auto-vac root page
和 Incremental-vacuum mode
都是vacuum
相关的设置,前者是auto_vacuum
模式使用,后者是incremental_vacuum
模式使用。当前者等于0时候,ptrmap
类型的页将会被从数据库文件中剔除,并且这两种模式都不会被支持。当前者不为0的时候,它指向的是最大的根页,储存着许多ptrmap
类型的页,并且此时由后者控制是auto_vacuum
还是incremental_vacuum
文本编码 Text encoding
- 1 for UTF-8
- 2 for UTF16LE
- 3 for UTF16BE
User version
这是给用户使用的版本号,sqlite3 并不使用它。这好像在系统升级的时候确实有用
Application ID
这个也是留给用户使用的,表示当前是由那个进程在使用。这个用处没这么大。
sqlite3 库使用的版本
Change counter for version number
和SQLite version number
,在0x5c和0x60存放的是sqlite3.so/sqlite3.dll在使用此数据库文件时候,存放的版本号,并且自增修改次数。用途不太明确,但是应该用来判断是否有不同版本的sqlite3库修改此文件吧。