テクノロジー · 実用ガイド

SQLite の database is locked:busy_timeout を増やす前に書き込み元とトランザクションを特定

SQLITE_BUSY をロックの時系列問題として扱い、書き込み接続、トランザクション段階、ジャーナル方式を確認してから待機や WAL を選びます。

SQLite の書き込み元がロックを保持し、別接続がタイマーと待つオリジナルイラスト。
オリジナルイラスト:timeout を増やす前に書き込み元とロック時間を特定します。 Watch at Ease

SQLite の「database is locked」は必要なロックを取得できなかったことしか示しません。どのプロセスや接続が競合を作ったかは別に証明する必要があります。

接続ごとの時系列を作り、短時間の競合と DEFERRED の読み取りから書き込みへの昇格失敗を分けて、最小の修正を検証します。

失敗時点の証拠を固定する

例外全文、数値コード、SQL、プロセス、スレッド、接続 ID、時刻を保存し、BEGIN、最初の書き込み、COMMIT、チェックポイント、復旧、終了のどこで失敗したかを記録します。

-wal や -shm を削除せず、主 DB だけをコピーしないでください。ファイルを開き得るアプリ、シェル、移行、ワーカー、エディタを列挙します。

接続ごとのトランザクション時系列を作る

接続、BEGIN、最初の読み取り、最初の書き込み、commit/rollback、文の終了、close を記録し、journal_mode、timeout、トランザクション方式も添えます。

ネットワークやファイル I/O を書き込みトランザクション内で待っているなら、再試行調整より先に外へ移します。

待機可能な競合と昇格失敗を分ける

SQLite は複数の読み取りを許しますが、書き込みトランザクションは一つです。DEFERRED が先に読むと後で昇格が必要で、別の書き込み元がいれば SQLITE_BUSY になります。

必ず書き込む処理が開始時に書き込み枠を取る設計に合う場合だけ BEGIN IMMEDIATE を使います。既存の書き込み元がいればこれも失敗します。

busy_timeout は短い競合だけに使う

timeout は待機時間を決めるだけで、別接続の commit、漏れたカーソルの close、長いトランザクションの短縮はしません。待機時間と保持者を測ります。

冪等操作か副作用を制御したトランザクション全体だけを再試行し、ジッターと上限を設けます。

WAL を万能な解除策にしない

WAL は読み取りと書き込みの重なりを改善しますが、書き込み元は一つのままです。排他モード、後片付け、復旧でも SQLITE_BUSY は起こり得ます。

全プロセスとファイルシステムの互換性を確認し、DB と WAL を安全に扱い、導入後もチェックポイントと書き込み時間を監視します。

最小の修正を適用して再検証する

文と接続を確実に閉じ、全出口で commit か rollback を行い、書き込みトランザクションを短くし、不可避な書き込み元を直列化します。

元の操作を ID と計時付きで再生し、二重書き込みがなく、予算超過が明示的に失敗することを確認します。継続的な多数書き込みにはサーバー型 DB も検討します。

読み取りトランザクションが単一書き込みゲートに到達し、WAL と有限待機へ分岐する概念イラスト。
オリジナル概念イラスト:ロック経路を分類してから IMMEDIATE、WAL、有限待機を選びます。 Watch at Ease