科技 · 实用指南
Python SQLite 备份卡住?提交事务前先检查两端连接
区分源端待提交写入、已占用的目标连接与外部锁竞争;单个事务标记不能决定修复动作。

Python 的 SQLite 备份可能一直重试,却不抛出熟悉的数据库被锁定异常。加上 commit() 能让某个示例恢复,也可能提交备份辅助函数无权决定的业务修改。应先弄清源端、目标端和未完成的工作。
我们用 12 个小型对照区分了复制停滞、目标立即拒绝和读取事务正常完成,也验证了 in_transaction=True 只是调查线索,不能直接等同于应当提交,或一律拒绝备份。
1. 改变事务状态前,记录两端角色
记录 Python、sqlite3.sqlite_version、实际调用、两端数据库路径,以及各连接的 autocommit、isolation_level 和 in_transaction。查清每个连接最后执行的 SQL 与事务负责人。使用名称唯一的独立候选目标;不要把备份目标指向正在使用的源库或需要保留的旧备份。
区分首次进度回调前失败,还是反复回调后仍未完成。把状态码、剩余页数与总页数一起记录。备份运行期间不要查询目标连接:SQLite 明确限制这时使用该连接。状态仍是 busy 时,即使剩余页数为零,也不能证明完成。
2. 不要把布尔标记当成诊断结果
在 Linux 的 CPython 3.12.14、SQLite 3.53.1 中,源端有未提交 INSERT 时反复返回状态 5,即 SQLITE_BUSY。诊断回调在三次尝试后停止操作。测试的事务负责人有意提交后,同一源端备份成功。另一个连接持有排他锁时也返回状态 5,因此仅靠该数字无法判断锁的负责人。
使用 autocommit=False 时,源端的空延迟事务和已经读取表的事务都能备份,in_transaction 始终为 True。同模式的新目标在尚未读取数据时也能成功;执行 SELECT 后再次备份,却在进度回调前报出“destination database is in use”。目标存在待提交写入时同样拒绝。连接角色和实际工作比单个标记更有辨别力。
3. 决定备份必须包含谁的数据
如果源端未提交修改必须进入备份,应由业务事务负责人按原意完成这组操作,再重试。不要让通用辅助函数仅为消除症状而提交。Python 文档说明,把 autocommit 改为 True 会提交待处理事务;对现有业务连接这样做,是实质性的数据决定。
对于文件数据库,也可以用专用只读连接备份已提交数据,而不提交写入方。我们的对照中,备份只有第 1 行,写入方仍能看到尚未提交的第 2 行。成功结果有意不含第 2 行。另开一个普通 :memory: 连接会得到不同数据库,因此不能把这种替代方式直接套到内存源库。
4. 在备份自己的回调中限制重试
本次核对的 CPython 实现会在 SQLITE_OK、SQLITE_BUSY 和 SQLITE_LOCKED 时循环。备份进度回调在每次步骤之后执行;回调抛出异常时,程序结束并放弃备份。连接的锁等待超时不等于整个备份的截止时间。应使用 backup(progress=...),不要与执行 SQL 的另一种进度处理器混淆。
若采用总耗时上限,在调用前用 time.monotonic() 记录截止点。回调中保存三个参数,超过截止点且状态不是 SQLITE_DONE 时抛出 TimeoutError。如果有进展但不断重启的工作也要受限,就不要刷新截止点。锁定文件对照把连接超时设为零、回调预算设为 0.03 秒,约 0.03 秒后抛出异常。这是在步骤之间协作检查,并不能硬性中断卡住的系统调用。
5. 将取消与备份验收分开
发生拒绝或回调异常后,关闭专用连接,并将候选排除在可用备份集合之外。不能因为文件存在、执行过回调或捕获了异常就将它转为正式备份。调用成功后,关闭目标,再重新打开这份确切候选,检查完整性和业务需要的数据;下方验收指南介绍后续步骤。
对照只使用小型本地文件库和内存库,以及回滚日志模式;未测试 WAL 持续写入、网络文件系统、ORM 或所有 Python 版本。独立的 Hermes 报告促成了 2026 年 8 月 16 日合并的修复,限制连续 busy 重试;该应用的策略不是通用总期限。保留源库,先明确完成条件,再安排下一次尝试。
