SQLite3: トランザクション中に更新処理を行うとSQLITE_BUSY(5)が発生する

iOS 13.3, XCode 10.3で確認。

テーブルロック時のSQLITE_BUSY(5)

SQLite3ではトランザクションによってテーブルロックがかかっている場合、そのテーブルを更新しようとするとSQLITE_BUSY(5)が発生する。

SQLITE_BUSY(5)については以下を参照。 www.sqlite.org

対処法

この問題について、SQLite3ではsqlite3_busy_timeoutを実行してタイムアウトまでの時間をセットすることで自動的にリトライしてくれる機能があり、これで回避することができる。

sqlite3_busy_timeoutについては以下を参照。 www.sqlite.org

トランザクション中に更新処理を行うと発生するSQLITE_BUSY(5)

BEGIN DEFERREDトランザクションを開始した場合に発生する。SQLite3ではトランザクションに以下の2種類あり、BEGIN DEFERREDでは最初に実行したSQLがSELECTなどのread系の場合はread transactionとして開始される。

タイプ 説明
read transaction 読み込み専用。 他のスレッド等から更新を行うことができる。トランザクション中はそれらの更新は見えず、更新前のデータが参照される。
write transaction 読み書き可能。 他のスレッド等から更新を行うことはできない。

その後、UPDATEなどのwrite系のSQLが実行されるとwrite transactionに切り替えられるが、その際、別のスレッド等がすでにデータベースを更新していると切り替えに失敗し、SQLITE_BUSY(5)が発生する。このエラーはsqlite3_busy_timeoutでは対応してくれない。また通常のSQLITE_BUSY(5)と異なり、リトライしても成功することは無い。

対処法

トランザクションの開始をBEGIN IMMEDIATEに変更する。この場合、最初からwrite transactionとして開始されるので、この時点でテーブルがロックされる。他のスレッドが同時にテーブルを更新する可能性があるので、sqlite3_busy_timeoutを実行しておくことでそれらは自動的にリトライが行われる。

SQLite3のトランザクションについては以下を参照。 www.sqlite.org