在预写日志记录模式下使用 System.Data.Sqlite 1.0.86.0(包括 SQLite 3.7.17),我在并发读取时遇到数据库锁,如果我正确理解 WAL,情况就不应该出现这种情况。我没有写或提交任何东西,并且ReadCommitted
正确使用了事务隔离模式来避免序列化读取。
SQLite DB(带有 WAL)在准备“选择”语句时被锁定 - 为什么?是一个类似的问题。唯一的答案是关于sqlite3_reset
在 each 之后调用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);
}
}
输出:
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;
你可以看到最后一个慢了一个数量级。如果在调试模式下执行,则根据运行情况在输出窗口中记录一次或多次:
SQLite 错误 (261): 数据库被锁定
您知道如何避免这种锁定吗?当然,在这个示例中,WAL 可以简单地关闭,但在实际项目中我不能:即使正在进行长时间的读取事务,我也需要潜在的写入来立即成功。