目标
默认回滚模式下,写事务期间所有读者都被阻塞,一次夜间导入就能让仪表盘瘫痪。WAL 模式反转了结构:写者向日志文件追加、读者快照主库,两边并发推进。再配 busy_timeout 让竞争排队等待而非报错、保持写事务短小,database is locked 的时代就结束了。
步骤要点
- 每个数据库执行一次 PRAGMA journal_mode=WAL,确认返回 wal。
- 在每个连接上统一设置 busy_timeout=5000 与 synchronous=NORMAL,让锁竞争等待重试而不是立刻抛 SQLITE_BUSY。
- 并发验证:后台开写事务的同一时刻执行 SELECT count(*),应立即返回,回滚模式下这不可能。
- 写事务用 BEGIN IMMEDIATE 前置抢占写者位,并按 id 范围分批保持事务短小,避免中途升级失败。
- 定期 PRAGMA wal_checkpoint(PASSIVE) 并监控 WAL 文件大小是否收敛在几十 MB 内,确认检查点跟得上写入。
排错提示
仍报 database is locked 就逐连接核对 journal_mode 是否都为 wal、是否存在阻塞检查点的长读事务;WAL 无限增长通常是有常驻读者让检查点无法推进,应定期关闭空闲读连接或用 TRUNCATE 检查点收尾;导入期间读者看到旧数据是快照隔离的正常行为,需要新鲜度就先结束读再重开。另外 WAL 不支持网络文件系统,库文件必须落在本地盘。
适用边界
WAL 仍是单写者模型,多个写者照样在写锁上排队;写者与读者并发无碍,但“多个写者并发”这个前提本身不成立。
代码示例
# Enable WAL once
sqlite3 app.db "PRAGMA journal_mode=WAL;"
# Reader unblocked while a write transaction is open
sqlite3 app.db "BEGIN; UPDATE events SET synced=1 WHERE synced=0;" &
sleep 0.2
sqlite3 app.db "SELECT count(*) FROM events;" # returns immediately