Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python 提示“database is locked”?先核对错误码,再选择正确重试范围

加长超时不能更新过期的 WAL 快照。先记录错误码、失败阶段和 Python 事务模式,再决定重试什么。

SQLite 错误阶段与正确重试范围的原创示意图
原创概念插图:区分写入资格、过期 WAL 快照和读取阻塞的提交;不是通用重试循环。 Watch at Ease

同样的 database is locked 提示,可能对应不同恢复任务:等待其他写入连接、丢弃过期读取快照,或者完成尚未成功的提交。盲目重复最后一条 UPDATE,可能继续使用过期判断,也可能把写入执行两次。

从抛出异常的连接入手。记录 Python 和实际 SQLite 版本、数据库身份、日志模式、事务控制设置,以及失败发生在 BEGIN、UPDATE 还是 COMMIT。改变应用事务策略前,先用临时、可丢弃的数据库复现。

保留扩展错误码和失败阶段

Python 3.11 起,源自 SQLite 的异常具有 sqlite_errorcode 和 sqlite_errorname。用 getattr(exc, "sqlite_errorcode", None) 与 getattr(exc, "sqlite_errorname", None) 记录它们;先保留完整值,再用 code & 255 求主要类别。SQLITE_BUSY 为 5,SQLITE_LOCKED 为 6,SQLITE_BUSY_SNAPSHOT 为 517。

BUSY 通常涉及其他连接;LOCKED 可涉及同一连接未完成的语句或共享缓存。隔离实验中,SELECT 游标只读取部分结果且未关闭,此时 DROP TABLE 返回 database table is locked、错误码 6。完成预定读取后结束或关闭实际游标,不要仅凭错误文字终止无关进程。

确认谁控制事务边界

Python 3.12 引入 autocommit。autocommit=True 时,即使通过 SQL BEGIN 开启事务,Connection.commit() 和 rollback() 也不起作用;该事务应使用显式 SQL COMMIT 或 ROLLBACK,并检查 in_transaction。autocommit=False 时,提交和回滚会立即开启另一笔 DEFERRED 事务。isolation_level 只控制 LEGACY_TRANSACTION_CONTROL。

不要把切换模式当作活动事务的修复捷径:把 autocommit 改为 True 会提交待处理工作。框架也可能负责这些边界。当前 Python 的连接上下文管理器会在提交失败后回滚,但 autocommit=True 时不做事务处理,而且不会关闭连接。已关闭的 2016—2021 年 CPython 缺陷是历史证据,不能据此说今天仍存在同一问题。

增加等待前,先选择重试范围

BEGIN IMMEDIATE 在操作尚未开始时返回 BUSY,可在有限重试预算内重新争取写入资格。缩短事务,持有事务时避免网络调用。BEGIN IMMEDIATE 成功,不等于任何日志模式下后续每项操作都保证不会 BUSY。

WAL 读取事务在另一连接提交后返回 517 时,应结束该事务、开启新事务、重新读取并重新计算判断。等待或只重试 UPDATE 无法刷新旧快照。在回滚日志模式中,COMMIT 被其他读取者阻塞而返回 BUSY 时,事务可能仍保持活动;确认文档所述条件解除后,重试 COMMIT,不要重复已经应用的写入。

临时双连接实验实际观察到什么

我们用 Python 3.12.14、实际 SQLite 3.53.1、timeout=0.05 秒、临时数据库文件和显式 SQL 边界运行了四个小实验,没有使用用户数据库。WAL 模式中,连接 A 读到计数 10,B 改为 11,A 随后写入返回 517。autocommit=True 下,A.rollback() 后 in_transaction 仍为 True。SQL ROLLBACK 将其清除;新的 BEGIN IMMEDIATE 重新读到 11,一次增加后提交为 12。

第一条连接占有写入资格时,第二条连接的 BEGIN IMMEDIATE 返回错误码 5,失败启动者没有活动事务。另一个 DELETE 日志实验中,开放读取事务阻塞另一连接的 COMMIT:错误码 5,写事务仍活动。结束读取事务后,只重试 COMMIT,初始 10 最终变为 11。这些是可复现的合成场景,不是对未知应用的诊断,也不证明生产环境安全。

用有上限、可观察的恢复流程收尾

连接超时是等待预算,不能修复过期快照或死锁。等待无法解除锁循环时,SQLite 可能不调用 busy handler。记录次数、耗时、错误码、阶段和事务状态;设置重试上限,预算耗尽后明确报告失败。不要捕获所有 OperationalError 并一律当作可重试竞争。

确认修复前,用另一连接核对预期持久值,确认目标事务结束,并重复相关竞争场景。autocommit=False 下,新开的空事务可能让 in_transaction 继续为 True,应结合模式解释。不要删除 WAL 或共享内存文件来“解锁”。若出现损坏、I/O 错误或事务归属不明,应停止此重试流程,调查对应原因。

Python autocommit 模式与显式事务结束的原创示意图
原创概念插图:True 模式的 Python 提交与回滚方法不生效;手动事务须显式 SQL 结束。 Watch at Ease