SQLiteで最も多いトラブルが database is locked。 特に C#(Microsoft.Data.Sqlite)で業務アプリを作ると、 同時書き込み・reader閉じ忘れ・長時間トランザクション が原因で頻発します。
この記事でわかること
・database is locked の根本原因
・同時書き込みを防ぐ設計
・WALモードの有効化
・トランザクションの正しい使い方
・非同期処理との組み合わせ方
・業務アプリ向けベストプラクティス
・database is locked の根本原因
・同時書き込みを防ぐ設計
・WALモードの有効化
・トランザクションの正しい使い方
・非同期処理との組み合わせ方
・業務アプリ向けベストプラクティス
1. database is locked の根本原因
SQLiteは「単一ファイルDB」のため、次の状況でロックが発生します。
- 複数の書き込みが同時に走った
- reader を閉じていない
- トランザクションが長時間占有している
- バックアップ中に書き込みが走った
重要:
SQLiteは「同時書き込みが1つしかできない」構造です。
これを理解して設計しないとロック地獄になります。
2. 最重要:書き込みは“直列化”する(同時に1つだけ)
SQLiteは同時書き込みに弱いため、 書き込み処理は必ず1つずつ順番に実行する必要があります。
■ C#での直列化(lock構文)
private static readonly object _writeLock = new();
public void SaveUser(User user)
{
lock (_writeLock)
{
using var con = new SqliteConnection(_cs);
con.Open();
var sql = "UPDATE Users SET Name=@name WHERE Id=@id";
using var cmd = new SqliteCommand(sql, con);
cmd.Parameters.AddWithValue("@name", user.Name);
cmd.Parameters.AddWithValue("@id", user.Id);
cmd.ExecuteNonQuery();
}
}
ポイント
- 書き込みは lock で排他制御
- 読み込みは並列でもOK(基本的に)
3. reader を必ず閉じる(using必須)
reader を閉じ忘れると、SQLiteは「読み込み中」と判断し、 書き込みがブロックされて locked が発生します。
■ NG例(reader閉じ忘れ)
var reader = cmd.ExecuteReader();
// reader.Close() を忘れる → locked地獄
■ OK例(usingで自動解放)
using var reader = cmd.ExecuteReader();
4. トランザクションは“短く”まとめる
トランザクション中は書き込みロックが保持されるため、 長時間のトランザクションは絶対にNG。
■ 正しいパターン
using var tran = con.BeginTransaction();
try
{
// INSERT/UPDATE をまとめて実行
tran.Commit();
}
catch
{
tran.Rollback();
}
ポイント
- トランザクションは短く
- 大量INSERTは1トランザクションで一気に
5. WALモードを有効化する(読み書き並列に強くなる)
SQLiteはデフォルトでは「ロックしやすい」 → WAL(Write-Ahead Logging)モードにすると改善します。
■ WALモードを有効化
using var con = new SqliteConnection(_cs);
con.Open();
using var cmd = new SqliteCommand("PRAGMA journal_mode=WAL;", con);
cmd.ExecuteNonQuery();
WALのメリット
- 読み込みと書き込みが同時に可能
- ロックが大幅に減る
- 業務アプリではほぼ必須
注意:
WALモードはネットワークドライブでは非推奨。
ローカルDBで使うのが基本。
6. 非同期処理(async/await)でUIフリーズを防ぐ
UIスレッドでDBアクセスすると、 処理が詰まってロックが発生しやすくなります。
■ 非同期で書き込み
await cmd.ExecuteNonQueryAsync();
■ DataTable.Load は同期 → Task.Run で逃がす
await Task.Run(() =>
{
dt.Load(reader);
});
7. バックアップ中のロック対策
ファイルコピー中に書き込みが走ると locked が発生します。
■ 最も安全:オンラインバックアップAPI
source.BackupDatabase(destination);
■ NG:書き込み中に File.Copy
これは高確率で locked を引き起こします。
8. database is locked を根本から防ぐ設計まとめ
- 書き込みは必ず直列化(lock)
- reader は using で確実に閉じる
- トランザクションは短く
- WALモードを有効化
- 大量INSERTは1トランザクションで一気に
- バックアップはオンラインバックアップAPI
- UIスレッドでDBアクセスしない(async/await)
まとめ:SQLiteのロックは“設計で防ぐ”のが最も効果的
- SQLiteは同時書き込みに弱い → 直列化で解決
- WALモードで読み書き並列に強くなる
- reader閉じ忘れ・長時間トランザクションは厳禁
- 業務アプリでは排他制御+非同期が必須
database is locked は「SQLiteの欠点」ではなく「設計の問題」です。 この記事の対策を組み合わせれば、ロック地獄から完全に抜け出せます。