4.3 WAL 文件结构与检查点


4.3 WAL 文件结构:帧、盐值、校验和与检查点

本节摘要:切到 WAL 模式后,xxx.db-wal 是系统里最重要的新文件。它的结构出人意料地简单:一个 32 字节文件头加一串"帧",每帧 24 字节帧头加一整页数据。本节逐字段解析这个结构,讲清盐值与校验和分别防止哪类损坏,检查点的三种力度,以及 WAL 文件无限增长的四个常见病因。

从一个真实 WAL 文件读起

一个刚提交过几个事务的 WAL 文件,前几十字节大致长这样(十六进制示意):

偏移 字节 含义 0-3 37 7f 06 82 或 37 7f 06 83 魔数,末位区分校验和的大端小端 4-7 00 2d e2 18 文件格式版本号 3007000 8-11 00 00 10 00 页大小 4096 12-15 00 00 00 03 检查点序号 16-23 12 ab 34 cd 56 78 9a bc 盐值 salt-1 加 salt-2 24-31 cc 12 34 56 78 9a bc de 首个校验和 32起 帧序列 ... 每帧 24 字节头 + 一整页

此后每一帧的结构完全一致:24 字节帧头加 4096 字节页数据。帧头四个字段:页号(这帧记录的是哪个页的新版本)、提交易(非零表示"本帧所在的提交已写库至此页数",零表示本帧之后还有同事务的帧)、两个盐值副本、两个校验和。整个文件就是"页的新版本流"——写入永远在文件尾部追加,永不修改已有帧。

图:WAL 文件帧结构

图:WAL 文件帧结构

读者怎么看:WAL 索引与快照

文件结构只解决了"新页在哪",还差"读者怎么挑版本"。答案在伴生的 <库名>-shm 共享内存文件里:它保存 WAL 索引——页号到 WAL 帧位置的映射表,加若干"读标记"(read-mark)。读事务开始时在某个提交帧位置落一个标记,此后它读任何页先查索引:WAL 里该页有比标记新的帧就用它开始时的那个版本,没有就直读主文件。不同时刻开始的读事务落在不同标记上,各自看到稳定一致的世界——这就是 WAL 模式的快照隔离,4.4 节会把它放进三库隔离对照表里。

检查点:把新世界搬回主文件

WAL 不能无限长,把帧内容回写主文件的动作用户。设页大小 4096,默认阈值下 WAL 长到约 4MB 就会触发自动检查点(wal_autocheckpoint 默认 1000 页)。三种力度:

  • PASSIVE:尽力而为——有读者挡路的部分跳过,不等待。自动检查点用这个力度,保证不阻塞前台。
  • FULL:等到能推进到最后一个提交帧,必要时等读者离场。
  • RESTART 与 TRUNCATE:进一步让下一个写事务从 WAL 头开始(RESTART)或直接把文件截为零(TRUNCATE),手动 PRAGMA wal_checkpoint(TRUNCATE) 常用于运维收尾。

PRAGMA wal_checkpoint(PASSIVE) 的返回值是一行四列:忙标记、WAL 总页数、已检查点页数、检查点后可安全覆盖的页数——排查 WAL 增长问题时先看这行。

⚠️ WAL 无限增长的四个常见病因:一是有个长命读事务把检查点挡在原地(快速诊断:查 PRAGMA wal_checkpoint 返回的忙标记);二是禁用了自动检查点或阈值设得过大;三是并发写太猛,追加速度长期高于 PASSIVE 检查点的推进速度;四是文件系统残留——进程异常退出后 WAL 未被清理,重新打开库会自动恢复,但前提是三件套(db、wal、shm)在同一目录里。

与另外两家的日志对照

把三家的"新世界落盘"放在一起看:SQLite 的 WAL 帧是整页快照追加,同一页可多版本共存于文件中,检查点负责归并;InnoDB 的 redo 是物理修改记录追加,页本身不进日志,由后台线程按检查点刷脏页;PostgreSQL 的 WAL(同名不同物)也是修改记录流,但配合"堆页多版本"的设计,恢复时重放修改即可。SQLite 的整页方案牺牲了日志体积(一次提交哪怕只改一行也要落一整页),换来的是恢复逻辑极简与实现极小——嵌入式哲学在日志层的又一次体现。

检查点的运维实操

三种力度的典型用法排成一张速查表:

场景 命令 期望结果
日常收尾(连接关闭前) PRAGMA optimize; 引擎按需做小检查点与统计更新
WAL 偏大的主动清理 PRAGMA wal_checkpoint(PASSIVE); 能推多少推多少,绝不阻塞业务
备份或拷贝前置 PRAGMA wal_checkpoint(TRUNCATE); WAL 归零,之后拷主文件即安全
诊断卡死原因 观察 PASSIVE 返回的四列 忙列为 1 即有读者挡路

返回行的读法再强调一次:第二列是 WAL 里现有帧数,第三列是本次成功回写主文件的帧数,第四列是回写后可以安全覆盖的帧数。第三列远小于第二列即"有东西挡着"——通常是某个长命读事务,去应用侧找那个迟迟不结束的连接,比反复跑检查点有效。

**shm 文件可以删吗?**连接全部关闭后,wal 与 shm 会自动清理(或保留一个归零的空壳,取决于版本与关闭方式),此时删除无害。运行期间删 shm 等于抽掉读者的版本地图,轻则查询报错,重则索引错乱——把它当主文件的一部分对待,与 8.4 节的"三件套"纪律一致。

常见问题速答

**WAL 模式能多人写吗?**能,但任意时刻仍只有一个写事务在写。多个连接可以轮流开写事务(配合 busy_timeout 排队),只是不能并行写。WAL 解放的是"读与写"的并行,不是"写与写"的并行——单写者模型没有变,变的是读者不再被写者阻塞。

**检查点会不会把性能拖垮?**PASSIVE 检查点每推进一步都会让出控制权,正常负载下感知不到。真正会拖垮性能的是"检查点债务":WAL 长期膨胀后,一次 FULL 或 TRUNCATE 检查点要回写的页数巨大,造成一次长 IO 尖峰。防法是让自动检查点正常工作、不让长读事务赖着不走——债务管理,与缓存预算同一思想。

本节要点回顾

  • WAL = 32 字节头 + 帧序列;每帧 24 字节头加一整页,提交易字段标记事务边界。
  • 盐值防"旧检查点周期的残留帧"混入,跨帧累计校验和防断电撕裂,两者共同定义了"只追加"的合法性。
  • 读事务靠 shm 里的读标记实现快照隔离;写者追加、检查点归并,读写互不阻塞。
  • WAL 增长先查三件事:长读事务、检查点配置、追加与检查点的速度差。

**切回 DELETE 模式后,WAL 文件去哪了?**切换命令会先把现有 WAL 的内容完整检查点回主文件,再移除 WAL 与 shm,保证切换零数据丢失——这也是为什么切换必须在没有活动事务时执行。做这条操作时留意返回值与文件系统上伴生文件的消失,确认干净收场,再进行后续的备份或分发动作。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U