SQLite 并发笔记:WAL 模式与 busy_timeout

数据库2026-09-01

最近一个 Python 写的小服务,多线程同时读写一个 SQLite 库,动不动就 database is locked。查了一圈资料加上自己实验,总结如下。

为什么会出现 database is locked

SQLite 默认的日志模式是 rollback journal:写操作会锁住整个数据库文件,其他连接的读写都要等。默认的等待时间又是 0,所以并发一上来直接报错。

WAL 模式

WAL = Write-Ahead Logging,写操作先写到独立的 wal 文件,不阻塞读连接。

开启方法很简单,只需要执行一次:

PRAGMA journal_mode=WAL;

开启后的效果:读写可以真正并发,读操作不再被写操作阻塞,性能提升非常明显。

busy_timeout

WAL 解决了读写并发,但多个连接同时写还是会冲突。这时候需要给每个连接设置等待时间:

conn = sqlite3.connect("app.db", timeout=15)
# 或者显式一点:
conn.execute("PRAGMA busy_timeout=15000")

这样写锁冲突时连接会等待最多 15 秒,而不是立刻报错。

一个真实的死锁案例

有个后台任务的流程是:开启写事务 → 循环处理数据 → 事务里调用了一个"写操作日志"的函数。而这个日志函数内部又开了新连接去写日志表。

结果就是:外层事务持有写锁,日志函数的新连接在等这个锁,而外层事务又在等日志函数返回。互相等待,死锁。日志里每 15 秒刷一次 database is locked。

修复思路:把嵌套的写操作挪到事务外面。先把日志信息收集到数组里,事务 commit 完成之后再逐条写入。改动很小,问题彻底解决。

小结

  • 多进程/多线程访问同一个 SQLite:WAL + busy_timeout 是标配
  • 绝对不要在事务里嵌套开新连接写同一个库
  • 写操作日志一定要在事务提交后再写

← 返回首页