Sqlite 数据库锁定在WAL模式下,只有读卡器

Sqlite 数据库锁定在WAL模式下,只有读卡器,sqlite,concurrency,system.data.sqlite,Sqlite,Concurrency,System.data.sqlite,在模式下使用System.Data.Sqlite 1.0.86.0(包括Sqlite 3.7.17),我在并发读取时遇到数据库锁定,如果我正确理解WAL,则不应出现这种情况。我没有写入或提交任何内容,并且正确使用ReadCommitted事务隔离模式来避免序列化读取 这是一个类似的问题。唯一的答案是在每个sqlite3\u步骤之后调用sqlite3\u reset,就我在源代码中看到的情况而言,这是由System.Data.Sqlite正确完成的 完整复制: 内部静态类程序{ 私有常量字符串Db

在模式下使用System.Data.Sqlite 1.0.86.0(包括Sqlite 3.7.17),我在并发读取时遇到数据库锁定,如果我正确理解WAL,则不应出现这种情况。我没有写入或提交任何内容,并且正确使用
ReadCommitted
事务隔离模式来避免序列化读取

这是一个类似的问题。唯一的答案是在每个
sqlite3\u步骤
之后调用
sqlite3\u reset
,就我在源代码中看到的情况而言,这是由System.Data.Sqlite正确完成的

完整复制:

内部静态类程序{
私有常量字符串DbFileName=“test.sqlite”;
私有静态只读字符串_connectionString=BuildConnectionString(DbFileName);
内部静态空隙总管(){
Delete(DbFileName);
ExecuteSql(“创建表测试(Id INT不为NULL,名称文本);”,true);
对于(int i=0;i<10;i++)
运行(()=>ExecuteSql(“从测试中选择Id、名称;”,false));
Console.ReadKey();
}
私有静态字符串BuildConnectionString(字符串文件名){
var builder=new-SQLiteConnectionStringBuilder{
数据源=文件名,
DateTimeFormat=SQLiteDateFormats.ISO8601,
DefaultIsolationLevel=IsolationLevel.ReadCommitted,
ForeignKeys=true,
JournalMode=SQLiteJournalModeEnum.Wal,
SyncMode=SynchronizationModes.Full
};
返回builder.ToString();
}
私有静态void ExecuteSql(字符串sql,bool commit){
秒表秒表=Stopwatch.StartNew();
使用(var connection=newsqliteconnection(_connectionString)){
connection.Open();
使用(SQLiteTransaction=connection.BeginTransaction(IsolationLevel.ReadCommitted)){
使用(SQLiteCommand=connection.CreateCommand()){
command.CommandText=sql;
command.ExecuteNonQuery();
}
如果(提交)
Commit();
}
}
秒表;
WriteLine(“{0}:{1}”,stopwatch.appeased,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可以简单地关闭,但在实际项目中我不能:即使长时间读取事务正在进行,我也需要潜在的写入立即成功。

经过调查,锁定数据库的不是读取,而是打开连接。正如我在再次阅读WAL文档后所理解的,即使是读者也必须具有对WAL文件的写访问权限。打开连接的简单事实比非WAL模式的成本要高得多。这个操作显然包括获取WAL文件的独占锁,即使是在很短的时间内


一个简单的解决方案是启用池(连接字符串中的
pooling=True
)。它在示例中没有任何效果,因为所有连接都是同时打开的,但在实际应用程序中,由于重用了现有连接,因此不再有任何锁。大多数简单的查询从5毫秒到不到1毫秒(在SSD上),并且“数据库已锁定”消息完全消失。

它总是第十个并发事务吗?如果使用默认隔离级别,它是否会更改?数据库文件是在网络上还是在本地磁盘上?不,它并不总是第十个,这取决于运行情况,通常在10个线程中发生2-3次。只有3个线程,我得到它的时间约50%(当然不是一个科学的措施)。数据库是本地的。SQLite的默认隔离级别是可序列化的,这意味着对于给定的事务(如果我正确获取了文档),根本没有并发性。您的sqlite3可能在编译时没有
HAVE_USLEEP
,这会使并发线程在重试之前等待几秒钟。