← 中文资源

简体中文版本

SQLite — database is locked

通过缩短写事务、启用 WAL 和 busy timeout、避免长读锁来诊断 SQLITE_BUSY 错误。

阅读英文原文

同一时间只有一个写者

SQLite 串行化写入。一个连接持有写事务时,另一个写者只能等待或失败。默认日志模式还可能让写者被读者阻塞。SQLITE_BUSY 表示等待策略已过期,或根本没有配置等待。

PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;

WAL 允许读者和写者并发推进,busy_timeout 让写者等待而不是立刻失败。每个连接都要设置两者;打开连接却不设置这些 pragma 的连接池会重新引入问题。WAL 对数据库文件持久,busy_timeout 则是每连接设置。

保持事务简短

最常见原因是在外部调用期间持有事务:

db.exec('BEGIN');
await fetch('https://slow.example.com'); // holds the write lock
db.exec('COMMIT');

把网络调用移到事务外。先准备数据,再 begin、写入、commit。要 finalize 语句以免持有锁,并避免在同一进程中长时间读取同时写入。

必要时串行化写入

如果多个进程必须写入,把写请求路由到单一进程或队列,或改用客户端/服务器数据库。网络文件系统会破坏 SQLite 的锁假设;不要把并发写入的数据库放在 NFS 或 SMB 上。对于读多的桌面或边缘负载,WAL 加单写者通常足够。

修复与验证

启用 WAL 和 busy timeout、缩短事务、finalize 语句。用并发写者验证数分钟,断言没有 SQLITE_BUSY。添加一个慢读者加写者的测试,确认 WAL 行为。对瞬时争用可以进行有界重试并记录日志;无界重试会掩盖永不提交的事务。