SQLite 明明支持并发,为什么你的程序还是频繁报 database is locked?这篇笔记把 WAL 模式的原理和几个最常见的坑一次讲清。
默认的回滚日志模式问题在哪
SQLite 默认使用回滚日志(rollback journal)。在这种模式下,写事务会对整个数据库文件加排他锁,写入期间所有读操作都被挡在外面。Web 服务这种「大量读 + 少量写」的场景,一个慢写就能让所有请求排队,表现出来就是间歇性的 SQLITE_BUSY。
WAL 模式改变了什么
执行一条语句即可切换:
PRAGMA journal_mode=WAL;
WAL(Write-Ahead Logging)把修改先追加写入独立的 -wal 文件,而不是直接改主库文件。带来的直接收益:
- 读写不再互斥:读事务看到的是自己开始时刻的快照,写事务往 WAL 追加,两者互不阻塞。
- 写入更快:追加写代替随机写,机械盘和云盘上差距尤其明显。
- 崩溃恢复更简单:重启后自动从 WAL 回放,不需要人工介入。
注意 WAL 是数据库文件级别的持久设置,切一次就够了,不需要每个连接都执行。
三个最常踩的坑
坑一:写和写仍然互斥。 WAL 只解决读写并发,同一时刻仍然只允许一个写事务。多线程/多进程同时写,第二个写会拿到 SQLITE_BUSY。正确做法是设置 busy_timeout(比如 5000 毫秒),让 SQLite 自己排队重试,而不是在业务层裸重试:
PRAGMA busy_timeout=5000;
坑二:长读事务拖垮 checkpoint。 WAL 文件需要定期把内容合并回主库(checkpoint),但只要有读事务还引用着旧快照,checkpoint 就无法推进,-wal 文件会一直膨胀。排查方向:找出那些「开了事务却忘了提交」的连接,尤其是 ORM 默认开启的隐式事务。
坑三:网络文件系统不支持。 WAL 依赖同主机的共享内存(-shm 文件),把数据库放在 NFS 或容器跨主机挂载卷上会出现未定义行为。这类部署要么退回回滚日志模式,要么换成真正的 C/S 数据库。
什么时候不该用 WAL
如果你的场景是一次性批量导入、只读镜像分发,或者数据库文件需要跨机器拷贝(拷贝时要连 -wal、-shm 一起处理,容易漏),回滚日志模式反而更省心。
一句话总结:WAL + busy_timeout 是绝大多数嵌入式场景的正确默认值,剩下的问题基本都出在长事务和部署环境上。
共同学习,写下你的评论
评论加载中...
作者其他优质文章