SQLite 的 WAL 一直变大:用两个连接找出长读事务
SQLite 应用的写入还在成功,旁边的 -wal 文件却持续增长。先检查是否有长读事务。WAL 模式允许读写同时进行,但读者可能继续依赖旧快照,使 checkpoint 无法完成回收。
1. 保持快照,再尝试截断 WAL
保存为 wal_lab.py,运行 python wal_lab.py。两个连接打开同一个临时文件,写连接关闭自动 checkpoint,让对比可重复。
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
path = Path(tmp) / "lab.db"
with closing(sqlite3.connect(path, isolation_level=None, timeout=0)) as writer:
assert writer.execute("PRAGMA journal_mode=WAL").fetchone() == ("wal",)
writer.execute("PRAGMA wal_autocheckpoint=0")
writer.execute("CREATE TABLE events (id INTEGER PRIMARY KEY, body BLOB)")
writer.execute("INSERT INTO events VALUES (1, zeroblob(1024))")
writer.execute("PRAGMA wal_checkpoint(TRUNCATE)")
with closing(sqlite3.connect(path, isolation_level=None)) as reader:
reader.execute("BEGIN")
assert reader.execute("SELECT count(*) FROM events").fetchone() == (1,)
for _ in range(30):
writer.execute("INSERT INTO events(body) VALUES (zeroblob(1024))")
assert reader.execute("SELECT count(*) FROM events").fetchone() == (1,)
blocked = writer.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
wal = path.with_name(path.name + "-wal")
assert blocked[0] == 1 and wal.stat().st_size > 0
print("reader snapshot: 1 row; writer committed: 31 rows")
print("checkpoint busy:", blocked[0])
reader.execute("COMMIT")
done = writer.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
assert done == (0, 0, 0) and wal.stat().st_size == 0
print("after reader commit:", done, "wal_bytes:", wal.stat().st_size)
assert reader.execute("SELECT count(*) FROM events").fetchone() == (31,)
本地实测输出如下。
reader snapshot: 1 row; writer committed: 31 rows
checkpoint busy: 1
after reader commit: (0, 0, 0) wal_bytes: 0
2. 生产环境先看 checkpoint 进度
PRAGMA wal_checkpoint(PASSIVE) 返回三个整数,依次是忙状态、WAL 中的页数和已 checkpoint 的页数。PASSIVE 会尝试推进 checkpoint,跳过需要等待的部分,所以第一列为 0 时还应比较后两列。对于持续写入的库,连续采样比单次文件大小更容易判断是否积压。
检查读取端有没有把事务带进网络调用、数据导出或等待用户输入的逻辑。一个游标或事务应该覆盖必要的读操作,然后及时结束。如果导出必须拿到同一快照,预留其 WAL 增长空间,并观察导出结束后 checkpoint 是否追上。
TRUNCATE 会尝试完成 checkpoint 并截断文件,可能等待其他连接。演练的 timeout=0 让它遇到冲突立即返回。线上先看忙状态、活跃事务和 I/O,再选择是否执行。手工删除 WAL 文件会绕过数据库恢复流程,应始终让 SQLite 管理相关文件。
小结
WAL 大小持续增长时,对照 checkpoint 进度和最长读事务。结束读事务后再次观察,确认积压可以回收。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| SQLite WAL | https://www.sqlite.org/wal.html | 并发读写、读快照与 checkpoint 饥饿 |
| SQLite PRAGMA | https://www.sqlite.org/pragma.html#pragma_wal_checkpoint | checkpoint 模式与返回字段 |
核验说明
- 本地 SQLite 3.45.1 用两个连接验证长读快照与截断恢复。实验关闭了自动 checkpoint,并使用 0 秒写连接等待时间。