《第一次把 SQLite 用对:WAL 模式、事务、fsync 与那些“突然失败”的提交》
一篇真正讲清楚 SQLite WAL 的工程实战博客
一、前言:为什么我第一次用 SQLite 就踩了 WAL 的坑?
很多人对 SQLite 的印象是:
“轻量、简单、嵌入式,随便用用就行。”
直到你:
- 数据量开始变大
- 有批量写入
- 有恢复 / 幂等要求
- 开始追求性能
然后你看到了一行“性能优化秘籍”:
PRAGMA journal_mode = WAL;
你一设,性能确实提升了
但随之而来的,是一堆“原来不报错、现在突然失败”的提交异常
database is locked
commit failed
busy
这篇文章,就是把这些问题从“为什么”到“怎么正确用”一次讲清楚。
二、SQLite 默认模式在干什么?(DELETE 模式)
在不显式设置的情况下,SQLite 使用的是 DELETE 模式。
一个事务在 DELETE 模式下的真实流程
BEGIN
→ 写 rollback journal(回滚日志)
→ fsync
→ 修改主数据库文件
→ fsync
→ 删除 journal
COMMIT
每一步用人话翻译:
-
rollback journal
“后悔药”:先把要改的数据原样备份
-
fsync
“我确认真的写进磁盘了”
-
修改数据库文件
真正改数据
-
删除 journal
事务成功,后悔药可以扔了
DELETE 模式的特点
| 特点 | 说明 |
|---|---|
| 安全 | 崩溃可回滚 |
| 简单 | 粗粒度锁 |
| 宽容 | 很多资源没关也能跑 |
| 慢 | 每次都改主库 |
DELETE 模式像一个“老好人”:
能忍就忍,能兜底就兜底。
三、WAL 模式是什么?(人话版)
WAL 全称:Write-Ahead Logging(写前日志)
一句话翻译 WAL
“写的时候不改账本,只写流水账;
账本什么时候抄,另说。”
WAL 模式下的三件套
| 文件 | 人话 |
|---|---|
.db |
正式账本(稳定、只读) |
.db-wal |
流水账(所有新写都追加在这) |
.db-shm |
共享记事板(告诉别人记到哪了) |
写数据时发生了什么?
- ❌ 不改
.db - ✅ 只往
.db-wal里 追加写
读数据时发生了什么?
- 先读
.db - 再读
.db-wal - 在内存里拼出一个“最新一致视图”
👉 读写可以并行
四、WAL 模式什么时候“合并”?读的时候会吗?
先给结论(非常重要)
❌ 读操作永远不会触发 WAL 合并(checkpoint)
WAL 合并(checkpoint)是什么?
把流水账里的内容,正式誊写回账本
SQLite 什么时候会合并?
-
自动 checkpoint
- WAL 修改页数达到阈值(默认 1000 页)
- 在写操作时触发
-
主动触发
PRAGMA wal_checkpoint(TRUNCATE); -
所有连接关闭时
-
SQLite 内部策略(不保证)
关键认知
读 = 看账,不誊账
写 = 可能触发誊账
五、为什么一开 WAL,你的提交就失败了?
这是几乎所有第一次用 WAL 的人都会遇到的。
原因只有一句话:
WAL 模式不再帮你“兜底不规范用法”
DELETE 模式下能侥幸成功的行为
- Reader 没及时关闭
- Command 生命周期过长
- 隐式事务
- 写事务边界不清晰
DELETE 模式:
“算了,我忍了。”
WAL 模式:
“不行,这会破坏一致性,拒绝。”
六、WAL 模式下,事务到底该怎么用?
一个非常重要的结论
❌ 不是“每一行 SQL 都要事务”
✅ 而是“每一个有业务意义的写操作,必须有清晰事务边界”
推荐事务使用规则表
| 场景 | 是否需要事务 |
|---|---|
| 纯 SELECT | ❌ |
| 批量 INSERT | ✅ |
| 状态更新(提交 / 回滚) | ✅ |
| 多条写构成一个业务动作 | ✅ |
| 隐式 UPDATE | ❌(不推荐) |
核心口诀
读随便读
写一定包
包就包清楚
一次事务 = 一次业务动作
七、两个常见 PRAGMA 的“人话解释”
1️⃣ PRAGMA synchronous = NORMAL
人话:
“写日志时,别每一步都硬等磁盘确认。”
含义:
- 可能在极端断电时丢最后 1~2 个事务
- 但数据库结构永远不会损坏
非常适合:
- 初始化数据
- 可重跑任务
- 幂等场景
2️⃣ PRAGMA temp_store = MEMORY
人话:
“临时用的草稿,别写硬盘,放内存。”
适合:
- 排序
- 分组
- 批量读取
八、工程级推荐配置(可直接用)
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA temp_store = MEMORY;
PRAGMA busy_timeout = 30000;
这是 SQLite 在工程场景下的“黄金组合”
九、WAL 模式下的最佳实践总结
你应该做的
- ✔️ 所有写操作显式事务
- ✔️ Reader / Command 生命周期极短
- ✔️ 初始化完成后主动 checkpoint 一次
- ✔️ 单 Connection 对应单执行流
你不该指望的
- ❌ WAL 帮你兜底脏代码
- ❌ 隐式事务永远安全
- ❌ 读操作会帮你清理 WAL
十、最后一句话总结
DELETE 模式像“老好人”
WAL 模式像“严格的工程师”它不是更难用,
而是让你第一次真正把 SQLite 用对。

浙公网安备 33010602011771号