SQLite WAL 模式实战:并发读写、检查点与常见坑

举报
yd_282288291 发表于 2026/11/08 08:49:09 2026/11/08
【摘要】 从 rollback journal 到 WAL 的并发模型讲起,整理 journal_mode、synchronous、busy_timeout 与 checkpoint 的配置要点,并总结备份、长事务、网络文件系统等常见坑,附一份可直接使用的 SQLite 配置模板。

一、背景:默认日志模式为什么容易卡

SQLite 默认使用 rollback journal。写事务开始后,数据库文件会被加锁,其他连接既不能写,也很难稳定地读;只有等事务提交、日志回滚完成,锁才会释放。单机小工具感觉不明显,但一旦放到 Web 服务、采集程序或桌面端多进程场景里,"偶发 database is locked"就会频繁出现。

WAL(Write-Ahead Logging)把修改先追加到 -wal 文件,再在合适的时机回写主库。它的核心收益不是"写入更快",而是读写不再互相阻塞:读连接看到的是某个已提交快照,写连接继续向后追加。对于"读多写少"的业务,这种并发模型通常比单纯调大缓存更直接。

二、开启方式与关键 PRAGMA

开启 WAL 只需要一条语句,但它是持久属性,会写进数据库头,不是每个连接都要重复设置:

PRAGMA journal_mode = WAL;

配套参数建议一起设置:

PRAGMA synchronous = NORMAL;      -- WAL 下的常用折中
PRAGMA busy_timeout = 5000;       -- 等锁最多 5 秒,避免立刻报错
PRAGMA wal_autocheckpoint = 1000; -- 每约 1000 页自动检查点
PRAGMA foreign_keys = ON;

synchronous=NORMAL 在 WAL 模式下通常足够:断电可能丢失最近若干已提交事务,但数据库不会损坏。如果业务对掉电一致性要求极高,再考虑 FULL,代价是每次提交都要 fsync。

三、并发模型:单写多读,不是多写

WAL 允许一个写事务和多个读事务同时进行,但同一时刻仍然只有一个写者。多个连接同时写,后到者只能等待,或者返回 SQLITE_BUSY。所以:

  • 读多写少的 API、报表、缓存服务,收益最明显;
  • 高频写入仍要做批量提交或单写队列,不能指望 WAL 把写也并行化;
  • 长读事务会阻止检查点回收 WAL,务必控制读事务的生命周期。

四、checkpoint:WAL 文件为什么会越来越大

WAL 不会无限停留在日志里,checkpoint 会把已提交的页写回主库,并在条件允许时截断 WAL。触发方式有三种:自动检查点、显式 PRAGMA wal_checkpoint(...)、以及关闭最后一个连接时的清理。

常见误区是只看主库大小,忽略 -wal 文件。若存在持续的长读事务,checkpoint 无法推进,-wal 会不断膨胀,磁盘占用和恢复时间都会受影响。排查时先看:

PRAGMA wal_checkpoint(PASSIVE);

返回结果里的三个数字分别是"是否被阻塞、日志帧数、已回写帧数"。如果长期被阻塞,优先找未结束的读事务,而不是盲目调大 wal_autocheckpoint。

五、几个容易踩的坑

  1. 忘记设置 busy_timeout:默认几乎立即返回 BUSY。应用层应该设置合理的等待时间,并给最终失败准备重试与降级。
  2. 把数据库放在网络文件系统:WAL 依赖共享内存和文件锁语义,NFS、SMB 上的行为并不可靠,生产环境应使用本地盘。
  3. 备份只拷贝主库:WAL 模式下还有 -wal 和 -shm。正确做法是用 VACUUM INTO 或 SQLite 在线备份 API,而不是直接 cp。
  4. 多进程 fork 后共用连接:连接不能跨 fork 共享,子进程应各自打开数据库。
  5. 只读介质或只读连接:WAL 数据库需要可写的 -shm,纯只读挂载可能无法打开,必要时使用 immutable=1 或先做快照。
  6. 长事务里做大量计算:写事务持续时间越长,其他写者等待越久,WAL 也越难回收。

六、一个可用的配置模板

import sqlite3

conn = sqlite3.connect("app.db", timeout=5, isolation_level=None)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("PRAGMA busy_timeout=5000")
conn.execute("PRAGMA wal_autocheckpoint=1000")
conn.execute("PRAGMA foreign_keys=ON")

写操作尽量短,批量插入用显式事务包起来;读操作避免在事务里做网络请求或耗时计算。需要强制收敛 WAL 时,在低峰期执行 PRAGMA wal_checkpoint(TRUNCATE),而不是在高频写入路径上反复调用。

七、监控建议

至少关注三个指标:-wal 文件大小、SQLITE_BUSY 出现次数、checkpoint 是否长期被阻塞。把这三项接入日志或监控后,大多数"数据库突然变慢"都能快速定位。WAL 不是银弹,但它用很小的配置成本换来了更清晰的并发边界,值得作为 SQLite 服务的默认起点。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。