

sqlite3.36版本 btree实现(三)- journal文件备份机制
source link: https://www.codedump.info/post/20211222-sqlite-btree-3-journal/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

sqlite3.36版本 btree实现(三)- journal文件备份机制
在上一节中(sqlite3.36版本 btree实现(二)- 并发控制框架),已经讲解了sqlite中的并发控制机制,里面会涉及到一个“备份页面”的模块:
- 备份所有在一个事务中会修改到的页面。
- 出错时回滚页面内容。
里面也提到,有两种备份文件的机制:journal文件,以及WAL文件。今天首先讲解journal文件的实现,它的效率会更低一些,也正是因为这个原因后续推出了更优的WAL机制。
sqlite中,可以使用PRAGMA journal_mode
来修改备份文件机制,包括以下几种:
- delete:默认模式。在该模式下,在事务结束时,备份文件将被删除。
- truncate:日志文件被阶段为零字节长度。
- persist:日志文件被留在原地,但头部被重写,表明日志不再有效。
- memory:日志记录保留在内存中,而不是磁盘上。
- off:不保留任何备份记录。
- wal:采用wal形式的备份文件。
其中,前面三种delete、truncate、persist都是使用journal文件来实现的备份,区别在于事务结束之后的对备份文件的处理罢了。
本节首先讲解journal文件,下一节讲解wal备份文件。
journal文件格式
journal文件的文件名规则是:与同目录的数据库文件同名,但是多了字符串“-journal”为后缀。比如数据库文件是“test.db”,那么对应的journal文件名为“test.db-journal”。
偏移量 大小 描述
0 8 文件头的magic number: 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7
8 4 journal文件中的页面数量,如果为-1表示一直到journal文件尾
12 4 每次计算校验值时算出来的随机数
16 4 在开始备份前数据库文件的页面数量
20 4 磁盘扇区大小
24 4 journal文件中的页面大小
这里大部分的字段都自解释了,不必多做解释,唯一需要注意的是随机数,因为这是用来后续校验备份页面的字段,这将在后面结合流程来说明。
紧跟着文件头之后,journal文件还有一系列页面数据组成的内容,其中每部分的结构如下:
偏移量 大小 描述
0 4 页面编号
4 N 备份的页面内容,N以页面大小为准,其中每页面大小在文件头中定义
N+4 4 页面的校验值
由上面分析可见,整个journal文件是这样来组织的:
- 28字节的文件头。
- 页面数据组成的数组,其中数组每个元素的大小为:4+页面大小(N)+4。
判断页面是否已经备份
启动一个写事务的时候,可能会修改多个页面,但是这其中可能有些修改,修改的是同一个页面的内容,因此这种情况下只需要对这个页面备份一次即可。
如何知道页面是否已经被备份过?页面管理器通过一个位图数据结构来保存这个信息:
计算页面校验值
计算一个页面校验码的流程在函数pager_cksum
中实现,其核心逻辑是:
- 以随机算出的校验值为初始值,这个初始值就是存在journal文件头中偏移量为[12,16]的数据。
- 从后往前遍历页面数据,每隔200字节取一个u32类型的值,累加起来。
有了这样的关联,进行数据恢复时就能马上通过文件头存储的随机数,计算出来页面的数据是否准确。
有了前面计算校验值、以位图来判断页面是否已经备份过的了解,现在开始将备份页面的流程。
每一次需要修改一个页面之前,都会调用函数pager_write
,这样就能在修改之前首先备份这个页面的内容。
要区分两种不同的页面:
- 如果页面编号比当前数据库文件的页面数量小,说明是已有页面,需要走备份页面的流程。
- 否则,说明是新增页面,新增的页面不需要备份,只需要修改该页面的标志位是需要落盘(
PGHDR_NEED_SYNC
),并且放入脏页面链表即可。
第二种情况是新增页面,没有备份的需求,这里就不做解释。
这里具体解释第一种情况,即备份已有页面的流程,其主要逻辑如下:
- 首先根据前面的
pInJournal
位图数据,传入页面编号,判断这个页面是否备份过,如果已经备份过,不做任何操作。 否则说明需要备份页面,将进入函数
pagerAddPageToRollbackJournal
中将该页面内容备份写入journal文件:- 调用前面提到的
pager_cksum
函数,计算页面的校验值。 - 按照上面解释的journal文件格式,依次写入页面编号、页面内容、第一步计算出来的校验值。
- 由于备份了页面,所以要把这个新增的备份页面编号写入
pInJournal
位图数据。
- 调用前面提到的
备份页面的例子
我们以一个例子来说明备份页面的流程,假设写事务执行时,情况如下:
- 当时数据库的页面数量为100,即有100个页面。
写事务执行时,依次做了如下的修改:
- 修改页面10的一处内容。
- 修改页面20的一处内容。
- 修改页面10的一处内容,注意这里跟第一次修改属于同一个页面的不同位置。
- 新增页面101。
那么,对照上面的流程,这四次页面修改在调用函数pager_write
时,情况是这样的:
- 修改页面10的一处内容:由于在备份页面位图中查不到页面编号为10的页面,且页面10小于当前数据库文件的页面数量100,属于修改当前已有页面,于是将这个页面备份到journal文件,完事了之后将这个页面编号10加入位图。
- 修改页面20的一处内容:类似的,也是备份了页面20的内容,同时将20加入位图。
- 修改页面10的一处内容:这一次虽然也是要修改已有页面,但是由于在位图中找到这个页面编号,说明在这一次事务中已经备份过这个页面了,于是不再需要备份操作,直接返回。
- 新增页面101:发现该页面的编号101,大于当前数据库页面数量100,属于新增页面,于是不进行备份,只是加入到脏页面链表中同时标记需要落盘。
即:在这一次写事务执行的过程中,虽然需要修改4处内容,实际备份文件两次,新增页面一次。
前面备份待修改页面的流程中,备份的页面内容只是写到了备份文件里,实际还并没有执行sync
操作强制落盘,只要没有落盘就还是存在备份数据损坏的情况。
在上一节的(sqlite3.36版本 btree实现(二)- 并发控制框架),备份文件内容落盘是放在第七步做的,此时对用户空间的页面内容的修改已经完成了,不清楚这一流程的可以回头再看看上一节的内容。
具体到journal文件的机制,这一步是放在函数pager_end_transaction
进行的,pager_end_transaction
函数就是上面介绍的:在事务修改完毕用户空间的页面之后,被调用。
本节讲解了journal文件的实现机制,从最早的sqlite btree实现时,备份页面的机制就一直使用journal机制,从这里的分析可以看到,这种机制很“朴素”,性能也并不好,所以后续在3.7版本的sqlite中引入了更优的WAL实现机制。
本节也并没有把所有journal文件实现机制都详细描述,只是把最核心的文件结构以及备份流程做了讲解,因为并不想在这个性能不高的机制上着墨更多,有兴趣的读者可以自行阅读相关代码。
Recommend
-
82
README.md better-sqlite3
-
64
-
11
0x00 前言 在上篇文章《渗透技巧——Windows下NTFS文...
-
7
使用Qt5.12.9的QGraphicsItem来实现俄罗斯方块,使用Sqlit3存储数据来进行游戏的回放,既然已经使用QT,就尽量用其组件,重写了原来的JSON封装及数据库操作接口实现。尽量复用已经实现的代码,所以只记录了每个方块的形状与姿态(旋转次数)及最终位置。...
-
7
sqlite3.36版本 btree实现(零)- 起步及概述 2021-12-17 在去年大体把btree以及b+tree算法流程研究了之后,我写了两篇博客: (鉴于b+tree只是btree...
-
11
sqlite3.36版本 btree实现(一)- 管理页面缓存 2021-12-17 页面管理模块中,很重要的一个功能是缓存页面的内容在内存中: 读页面:...
-
2
按照之前起步阶段对sqlite btree整体架构的分析,“页面管理模块”分为以下几个子模块: 页面缓存管理。 崩溃恢复,又分为以下两种实现: journal文件。 WAL文件。 页面管理模块。
-
4
前面两节,分别讲解了sqlite中写入事务时的并发控制框架,以及journal备份文件的实现机制。 回忆一下journal备份文件的实现: 每次一个新的写事务开始之前,要首先写journal文件的文件头。 写事务过程中,如果修改了哪个页面,...
-
3
sqlite3.36版本 btree实现(五)- Btree的实现 2022-02-01 《sqlite3.36版本 btree实现》系列文章: 前面的内容里,详细介绍了页面管理器部分的内容...
-
5
第三期 · 使用 Vue 3.1 + TailWind.CSS + Axios + Golang + Sqlite3 实现简单评论机制
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK