本节摘要:
PRAGMA journal_mode决定日志的物理形态:DELETE、TRUNCATE、PERSIST、MEMORY、WAL 五种模式是五套落盘策略。本节逐一拆解每种模式下"一次提交"的磁盘行为,给出对照决策表,并厘清它与 synchronous 参数的分工——一个管"日志长什么样",一个管"等多久才算提交"。
四种回滚系模式共享 4.1 节的账本逻辑,差别只在"账本文件怎么处理":
| 模式 | 提交后 journal 的命运 | 断点恢复依据 | 磁盘行为特征 |
|---|---|---|---|
| DELETE(默认) | 整个删除 | 文件不存在即无未完事务 | 反复创建删除文件,目录元数据开销大 |
| TRUNCATE | 截断为零字节 | 头部零即视为作废 | 免目录操作,适合嵌入式闪存 |
| PERSIST | 文件保留,头部字段抹零 | 头部校验字段失效即作废 | 免目录与尺寸变更,重用同尺寸文件 |
| MEMORY | 日志只存内存 | 无——断电即丢回滚能力 | 最快但破坏持久性,仅测试用 |
| WAL | 没有回滚 journal,改用 WAL 追加 | WAL 盐值与校验和 | 读写并行,检查点回写主文件 |
三种"文件保留"变体(TRUNCATE、PERSIST)的存在理由主要是嵌入式与闪存设备:创建和删除文件需要修改目录项,在 SD 卡类的存储上既慢又加剧磨损。把它们改成"反复覆写同一个文件",省掉的是元数据操作。桌面与服务器场景的闪存同样受益——这也是不少移动端框架默认选 TRUNCATE 或 WAL 的原因。
切换动作本身值得演示:
PRAGMA journal_mode; -- 查询当前模式:delete PRAGMA journal_mode = WAL; -- 返回 wal 表示切换成功 PRAGMA journal_mode; -- 确认:wal
注意两件事。其一,切换到 WAL 是持久设置——它写进了第 1 章读过的文件头 18/19 字节(读写版本号变成 2),下次打开仍是 WAL;切回 DELETE 同理。其二,切换必须在没有未完成事务时进行;从任何模式切到 MEMORY 都是危险动作,等于宣布"我不再要求崩溃一致性"。
切换到 WAL 后,写入路径发生结构性变化:提交时新页不再覆盖主文件,而是追加进 <数据库文件名>-wal 伴生文件;主文件在检查点(checkpoint)之前保持旧世界。这个一换带来三连变化:

journal 模式回答"日志长什么样",synchronous 回答"等多久才算提交",两者正交组合:
| synchronous | 行为 | WAL 模式下的实际含义 |
|---|---|---|
| FULL(默认回滚系) | 每次提交等待 fsync | 最安全,最慢 |
| NORMAL | 关键时机等待 | WAL 下推荐:断电最多丢最近事务,库不损坏 |
| OFF | 几乎不等 | 可能在断电时损坏文件,仅可丢弃数据可用 |
WAL 模式下官方明确推荐 synchronous=NORMAL:因为 WAL 是顺序追加日志,即使最近的事务在断电中丢失,主文件与 WAL 的结构仍完整,库一定能打开——这与回滚系模式下 OFF 的风险等级完全不同。移动端与桌面应用的标准组合是 journal_mode=WAL 加 synchronous=NORMAL。
⚠️ 常见误操作:把 MEMORY 模式当性能优化用在生产库上。它确实快,但断电后数据库文件可能处于不可恢复的损坏状态——省下的是毫秒,赔上的是整库。
**切到 WAL 返回的还是 delete,为什么?**两个常见原因。一是连接里有未完成的事务——切换必须在事务边界上,把活动事务收尾再切。二是文件系统不支持 WAL 依赖的共享内存语义(常见于网络盘与部分虚拟化挂载),引擎会拒绝切换并保持原模式;这种环境下 WAL 也确实不该用,4.3 节的检查点机制在共享内存缺失时无法保证正确性。
**移动端框架为什么几乎都默认 WAL?**移动负载是典型的"界面读加后台写"并发形态,WAL 让两者互不拖累;顺序追加对闪存寿命友好;NORMAL 同步级别在设备断电(电池拔出、崩溃重启)场景下只丢最后事务不损坏库——三条好处都精确命中移动端痛点。桌面浏览器的 WebAssembly 环境同样把 WAL 作为推荐配置。
**OFF 模式什么时候用?**只用于两类数据:完全可重建的数据(缓存表、可重算的派生表)和一次性测试库。它的"快"来自根本不记账——不仅断电丢事务,任何崩溃都可能留下结构损坏。把 OFF 当性能优化用在不满足这两个前提的库上,省下的是毫秒,赌上的是整库。
**PERSIST 与 TRUNCATE 怎么选?**两者都保留 journal 文件,差异在提交后的处理:TRUNCATE 把文件截为零字节(尺寸变化,但目录项不动),PERSIST 只抹掉文件头的有效标记(连尺寸都不变)。闪存上两者都避免了创建删除的元数据写;PERSIST 更极致——连文件系统的长度变更都省了,代价是磁盘上长期躺着一个与库等大的 journal 占位文件。空间敏感的设备选 TRUNCATE,写放大敏感的选 PERSIST。
**一个事务里能混用模式吗?**不能,journal_mode 是库级持久属性,切换必须在事务边界完成。但库与库可以不同模式——ATTACH 多个库时,主库 WAL、附属库 DELETE 是合法组合,只是跨库事务会退化为各库独立日志的协调模式,性能与原子性保证都弱于单库事务。需要跨库一致性的场景,先考虑合并成单库加命名空间隔离(表前缀或 schema 约定),再考虑 ATTACH。
下一节进入 WAL 文件内部:帧怎么组织,检查点怎么回收,盐值和校验和各防什么。