科技 · 實用指南
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 重試;該應用的策略不是通用總期限。保留來源,明訂完成條件,再安排下一次嘗試。
