Pager 生命周期函数:固定页面、运行事务以及在 SQLite 中使其持久化

发布: (2026年1月31日 GMT+8 02:15)
8 min read
原文: Dev.to

Source: Dev.to

你好,我是 Maneshwar。我正在开发 FreeDevTools online,目前的目标是构建“一站式开发工具、技巧代码和 TLDR 汇总”——一个免费、开源的中心,开发者可以快速找到并使用工具,无需在互联网上四处搜索。

在上一篇文章中,我们走遍了 pager 的 入口点:打开数据库、获取页面、声明写入意图以及准备日志记录。
今天,我们进入 控制函数,即管理 页面生命周期事务边界提交 vs 回滚 的函数。这正是 pager 不再仅仅是缓存管理器,而完整展现为 SQLite 事务引擎的地方。

固定页面:sqlite3PagerRefsqlite3PagerUnref

一旦页面交给树模块,分页器必须确保该页面不会在其下方消失。这时引用计数就派上用场了。

sqlite3PagerRef:固定页面

此函数会递增页面的 引用计数 并将页面标记为 已固定(pinned)。

已固定的页面不可触碰:

  • 不会被驱逐
  • 不会被回收
  • 不会被覆盖

已固定页面示意图

只要引用计数非零,分页器就保证该页面的内存保持有效。

sqlite3PagerUnref:释放固定

此函数会递减引用计数。当计数降至零时,页面变为 未固定(unpinned),可以被复用;它可能会被放入缓存空闲链表。

这里会出现一种微妙但关键的行为:当 所有页面 都变为未固定时

  • 分页器会释放对数据库文件的 共享锁
  • Pager 对象返回到中性、空闲状态

分页器释放锁示意图

这就是 SQLite 在不需要时避免持有锁的方式,而无需上层显式调用锁操作。

开始写事务:sqlite3PagerBegin

此函数标记 显式写事务 的开始。

它的作用

  • 在数据库文件上获取 保留锁
  • 打开回滚日志(除非数据库是临时的)
  • 将 pager 转换为写模式

如果数据库已经被保留用于写入,则此函数会成为 无操作——这点很重要,因为 sqlite3PagerWrite 可能已经启动了隐式写事务,而 SQLite 永远不会不必要地重复工作。

pager 也可能立即获取 独占锁,而不是等到实际写入开始时才获取。

Write transaction lock options illustration

此选择会影响并发性和延迟,并且完全由 pager 管理。

提交第一阶段:确保数据持久性

SQLite 在 两个不同的阶段 提交。第一阶段由以下函数处理:

sqlite3PagerCommitPhaseOne

这是 持久性阶段。分页器:

  1. 在数据库头部递增 file‑change‑counter(文件更改计数器)
  2. 将回滚日志同步到磁盘
  3. 将缓存中的所有脏页写入数据库文件
  4. 同步数据库文件本身

在此阶段之后,数据库文件已包含新数据,而日志仍然存在,因此仍可进行恢复。此时事务是 崩溃安全 的,但尚未最终完成。

提交第一阶段示意图

提交第二阶段:宣告胜利

第二阶段完成提交:

sqlite3PagerCommitPhaseTwo

此函数最终确定日志文件——删除、截断或使其失效。一旦完成:

  • 不再需要恢复
  • 事务正式完成
  • 锁可以安全降级

此分阶段设计使 SQLite 能在提交序列的任何点上抵御崩溃。

Commit phase two illustration

回滚:无错误撤销

当出现错误或应用程序请求时,pager 会走另一条路径。

sqlite3PagerRollback

此函数:

  • 从回滚日志中恢复原始页面内容
  • 还原所有内存中的页面
  • 完成日志的收尾工作
  • 将独占锁降级为共享锁

两个重要保证

  • 回滚不会失败
  • 数据库保持一致状态

无论执行期间情况多么混乱,回滚总会成功。这是 SQLite 最强的正确性承诺之一。

保存点:嵌套安全网

SQLite 并不把事务视为平面结构。每条 SQL 语句都在一个保存点内部运行,应用程序也可以自行定义保存点。分页器为此提供了两个函数。

sqlite3PagerOpenSavepoint

此函数:

  • 创建一个新的保存点处理器
  • 记录当前回滚日志的位置
  • 捕获当时的数据库状态

保存点会 堆叠——多个保存点可以共存。

sqlite3PagerOpenSavepoint diagram

sqlite3PagerSavepoint – 释放或回滚

同一个函数根据请求执行 两种截然不同的操作

保存点释放

  • 销毁保存点处理器
  • 保留自保存点以来的所有更改

保存点回滚

  • 将数据库状态恢复到保存点时的状态
  • 撤销其后所做的所有更改
  • 删除已回滚的保存点以及所有更新的保存点

该机制使 SQLite 能够回滚单条语句,而无需中止整个事务,也无需重新打开文件或重置锁。仍然全部由分页器处理。

sqlite3PagerSavepoint diagram

更大的全局

如果现在放大视角,模式应该一目了然。分页器:

  • 拥有页面的生命周期
  • 拥有事务边界
  • 拥有日志记录
  • 拥有持久性
  • 拥有恢复
  • 拥有回滚

更高层只 请求 操作。它们从不强制正确性;而是依赖分页器来实现。

包装分页器章节

在过去的几篇文章中,我们已经从以下内容转变:

  1. 日志
  2. 事务
  3. 保存点
  4. 将所有内容粘合在一起的分页器函数

我关于 SQLite 的实验和实操示例位于此处:
lovestaco/sqlite – sqlite‑examples

参考文献

  • SQLite Database System: Design and Implementation – Sibsankar Haldar.
  • FreeDevTools – 免费开发者工具集合。

FreeDevTools banner

👉 查看:

欢迎任何反馈或贡献
它是在线的、开源的,随时可供任何人使用。

在 GitHub 上给它加星:

Back to Blog

相关文章

阅读更多 »