11

System.Data.Sqlite 1.0.86.0 (SQLite 3.7.17 を含む) をWrite-Ahead Loggingモードで使用すると、同時読み取り中にデータベースのロックが発生しますが、WAL を正しく理解していれば発生しないはずです。私は何も書いたりコミットしたりしておらず、ReadCommitted読み取りのシリアル化を避けるためにトランザクション分離モードが正しく使用されています。

「選択」ステートメントを準備するときに SQLite DB (WAL を使用) がロックされる - なぜですか? 同様の問題です。唯一の答えは、ソースコードで見た限り、 System.Data.Sqlite によって正しく行われるsqlite3_reseteach の後に呼び出すことについてです。sqlite3_step

完全再現:

internal static class Program {

    private const string DbFileName = "test.sqlite";
    private static readonly string _connectionString = BuildConnectionString(DbFileName);

    internal static void Main() {
        File.Delete(DbFileName);
        ExecuteSql("CREATE TABLE Test (Id INT NOT NULL, Name TEXT);", true);
        for (int i = 0; i < 10; i++)
            Task.Run(() => ExecuteSql("SELECT Id, Name FROM Test;", false));
        Console.ReadKey();
    }

    private static string BuildConnectionString(string fileName) {
        var builder = new SQLiteConnectionStringBuilder {
            DataSource = fileName,
            DateTimeFormat = SQLiteDateFormats.ISO8601,
            DefaultIsolationLevel = IsolationLevel.ReadCommitted,
            ForeignKeys = true,
            JournalMode = SQLiteJournalModeEnum.Wal,
            SyncMode = SynchronizationModes.Full
        };
        return builder.ToString();
    }

    private static void ExecuteSql(string sql, bool commit) {
        Stopwatch stopwatch = Stopwatch.StartNew();
        using (var connection = new SQLiteConnection(_connectionString)) {
            connection.Open();
            using (SQLiteTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted)) {
                using (SQLiteCommand command = connection.CreateCommand()) {
                    command.CommandText = sql;
                    command.ExecuteNonQuery();
                }
                if (commit)
                    transaction.Commit();
            }
        }
        stopwatch.Stop();
        Console.WriteLine("{0}: {1}", stopwatch.Elapsed, sql);
    }

}

出力:

00:00:00.1927492: CREATE TABLE Test (Id INT NOT NULL, Name TEXT);
00:00:00.0054247: SELECT Id, Name FROM Test;
00:00:00.0055334: SELECT Id, Name FROM Test;
00:00:00.0056022: SELECT Id, Name FROM Test;
00:00:00.0054860: SELECT Id, Name FROM Test;
00:00:00.0053894: SELECT Id, Name FROM Test;
00:00:00.0056843: SELECT Id, Name FROM Test;
00:00:00.0006604: SELECT Id, Name FROM Test;
00:00:00.0006758: SELECT Id, Name FROM Test;
00:00:00.0097950: SELECT Id, Name FROM Test;
00:00:00.0980008: SELECT Id, Name FROM Test;

最後の 1 つは桁違いに遅いことがわかります。デバッグ モードで実行すると、実行に応じて、次の内容が出力ウィンドウに 1 回以上記録されます。

SQLite エラー (261): データベースがロックされています

このロックを回避する方法はありますか? もちろん、このサンプルでは WAL を簡単にオフにすることができますが、実際のプロジェクトではできません。長時間の読み取りトランザクションが行われている場合でも、すぐに成功する可能性のある書き込みが必要です。

4

1 に答える 1

13

調査の結果、データベースをロックするのは読み取りではなく、単に接続を開くことです。WAL のドキュメントをもう一度読んで理解したところ、読者であっても WAL ファイルへの書き込みアクセス権が必要です。接続を開くという単純な事実は、非 WAL モードよりもはるかにコストがかかります。この操作には、非常に短い期間であっても、WAL ファイルの排他ロックの取得が含まれているようです。

簡単な解決策は、プール (Pooling=True接続文字列内) を有効にすることです。すべての接続が同時に開かれるため、サンプルでは効果がありませんが、実際のアプリケーションでは、既存の接続が再利用されるため、ロックはもうありません。ほとんどの単純なクエリは 5 ミリ秒から 1 ミリ秒未満 (SSD 上) になり、「データベースがロックされています」というメッセージは完全になくなりました。

于 2013-06-15T14:58:34.597 に答える