数据库锁定在WAL模式下,只有读者

Jul*_*ain 10 sqlite concurrency system.data.sqlite

Write-Ahead Logging模式下使用System.Data.Sqlite 1.0.86.0(包括SQLite 3.7.17),我在同时读取时遇到数据库锁定,如果我正确理解WAL,则不应该这样.我没有编写或提交任何内容,并且ReadCommitted正确使用事务隔离模式来避免序列化读取.

准备"选择"状态时,SQLite DB(带WAL)被锁定 - 为什么?是一个类似的问题.唯一的答案是关于sqlite3_reset每个之后的调用sqlite3_step,这是由我在源代码中看到的System.Data.Sqlite正确完成的.

完全复制:

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

输出:

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;
Run Code Online (Sandbox Code Playgroud)

你可以看到最后一个慢一个数量级.如果在调试模式下执行,则输出窗口中会记录以下内容一次或多次,具体取决于运行:

SQLite错误(261):数据库已锁定

你知道如何避免这种锁定吗?当然在这个示例中,WAL可以简单地关闭,但在实际项目中我不能:我需要潜在的写入才能立即成功,即使长时间读取事务正在进行.

Jul*_*ain 12

经过调查,它不会读取锁定数据库,而只是打开连接.据我所知,在再次阅读WAL文档之后,即使是读者也必须具有对WAL文件的写入权限.打开连接的简单事实比非WAL模式具有更高的成本.此操作显然包括获取WAL文件的独占锁定,即使它是在很短的时间内.

一个简单的解决方案是启用池(Pooling=True在连接字符串中).它对样本没有任何影响,因为所有连接都是同时打开的,但在现实世界的应用程序中,由于现有连接被重用,因此不再有任何锁定.大多数简单的查询从5毫秒到不到1毫秒(在SSD上),"数据库被锁定"的消息完全消失了.