← 中文资源

简体中文版本

为读写并发调优 SQLite WAL 模式

启用 WAL 日志模式,设置 busy_timeout 与 synchronous 参数,验证写事务提交期间读者不被阻塞。

阅读英文原文

目标

默认回滚模式下,写事务期间所有读者都被阻塞,一次夜间导入就能让仪表盘瘫痪。WAL 模式反转了结构:写者向日志文件追加、读者快照主库,两边并发推进。再配 busy_timeout 让竞争排队等待而非报错、保持写事务短小,database is locked 的时代就结束了。

步骤要点

  1. 每个数据库执行一次 PRAGMA journal_mode=WAL,确认返回 wal。
  2. 在每个连接上统一设置 busy_timeout=5000 与 synchronous=NORMAL,让锁竞争等待重试而不是立刻抛 SQLITE_BUSY。
  3. 并发验证:后台开写事务的同一时刻执行 SELECT count(*),应立即返回,回滚模式下这不可能。
  4. 写事务用 BEGIN IMMEDIATE 前置抢占写者位,并按 id 范围分批保持事务短小,避免中途升级失败。
  5. 定期 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