“database is locked” 的常见建议是“调大 timeout”或“开 WAL”。这篇用 Python 3.15 自带的 SQLite 3.54.1 在本机逐个验证:到底是谁挡了谁、timeout 什么时候不起作用、忘了 commit 的后果,以及 8 个线程同时读后写时各种配置的真实失败数。
B 的 timeout=0:失败 OperationalError: database is locked,等了 0.00 s B 的 timeout=1:失败 OperationalError: database is locked,等了 1.06 s B 的 timeout=5:成功,等了 3.06 s
另一个连接持有写锁时,等待时间取决于 timeout。
写者持有未提交写时,读者 SELECT: DELETE 模式 -> ok | WAL 模式 -> ok 读者游标没读完时,写者 INSERT(自动提交): DELETE 模式 -> OperationalError: database is locked | WAL 模式 -> ok 两个写者:A 持有写锁,B INSERT: DELETE 模式 -> OperationalError: database is locked | WAL 模式 -> OperationalError: database is locked
WAL 让读写互不阻塞,但同一时刻仍只有一个写者。
BEGIN(DEFERRED)+SELECT 之后,timeout=10 秒:B 的 INSERT -> OperationalError: database is locked,用时 0.1 ms;A 的 INSERT -> ok,用时 0.00 s 改用 BEGIN IMMEDIATE(A 持有 0.5 s):B 的 BEGIN IMMEDIATE 排队等了 0.57 s(timeout=10 的保护有效),之后 SELECT 和 INSERT 都成功,两行都写进去了:2 行
先读后写的延迟事务升级写锁失败时不等 timeout,用 BEGIN IMMEDIATE。
A 默认事务模式 INSERT 后没有 commit:A.in_transaction = True B(timeout=1)INSERT -> OperationalError: database is locked,等了 1.06 s A.commit() 之后 B 再 INSERT -> ok autocommit=True:A INSERT 之后 B INSERT -> ok,用时 0.3 ms
Python 默认事务模式下 INSERT 不 commit 会一直握着写锁。
DELETE 模式,timeout=0,BEGIN(默认延迟):成功写入 185/1600 行,失败 1415 次 {'database is locked': 1415},总用时 0.15 s
DELETE 模式,timeout=5(Python 默认),BEGIN:成功写入 1600/1600 行,失败 0 次,总用时 0.76 s
DELETE 模式,timeout=30,BEGIN IMMEDIATE:成功写入 1600/1600 行,失败 0 次,总用时 0.64 s
WAL 模式,timeout=30,BEGIN IMMEDIATE:成功写入 1600/1600 行,失败 0 次,总用时 0.21 s
WAL 模式,timeout=5,BEGIN(只改日志模式):成功写入 1600/1600 行,失败 0 次,总用时 0.17 s
上面的事务只有一条 INSERT。真实代码常常是“先读再写”(SELECT 之后再 INSERT/UPDATE),下面加上 SELECT max(id) 再试:
读后写:DELETE 模式,timeout=30,BEGIN:成功写入 1436/1600 行,失败 164 次 {'database is locked': 164},总用时 0.56 s
读后写:DELETE 模式,timeout=30,BEGIN IMMEDIATE:成功写入 1600/1600 行,失败 0 次,总用时 0.95 s
读后写:WAL 模式,timeout=30,BEGIN:成功写入 228/1600 行,失败 1372 次 {'database is locked': 1372},总用时 0.11 s
读后写:WAL 模式,timeout=30,BEGIN IMMEDIATE:成功写入 1600/1600 行,失败 0 次,总用时 0.14 sWAL + 调大 timeout 不够,读后写必须 BEGIN IMMEDIATE。