汇集只读 SQLite 连接用于读写访问

4

使用的NuGet包:System.Data.SQLite.Core,1.0.98.1

问题:我的程序中有时会启用SQLite连接池,并在某些情况下只读取数据库。通常情况下一切都正常,但如果有大量混合只读/读写请求,程序将出现以下异常:

Unhandled Exception: System.Data.SQLite.SQLiteException: attempt to write a readonly database
attempt to write a readonly database
at System.Data.SQLite.SQLite3.Reset(SQLiteStatement stmt)
at System.Data.SQLite.SQLite3.Step(SQLiteStatement stmt)
at System.Data.SQLite.SQLiteDataReader.NextResult()
at System.Data.SQLite.SQLiteDataReader..ctor(SQLiteCommand cmd, CommandBehavior behave)
at System.Data.SQLite.SQLiteCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery(CommandBehavior behavior)
at System.Data.SQLite.SQLiteCommand.ExecuteNonQuery()

如果我禁用连接池,程序就能正常工作。我猜测是使用只读连接池来进行读写操作。我有什么遗漏吗?这是期望的行为吗?
最小复现代码(它在插入或删除时失败)。如果我引入延迟,例如Thread.Sleep(10000),它就能正常工作。如果我移除循环,它也能正常工作。
const string DbFilePath = "test.sqlite";
string readOnlyConnectionString = new SQLiteConnectionStringBuilder
    {
        DataSource = DbFilePath,
        Pooling = true,
        ReadOnly = true
    }.ConnectionString; // data source=test.sqlite;pooling=True;read only=True
string readWriteConnectionString = new SQLiteConnectionStringBuilder
    {
        DataSource = DbFilePath,
        Pooling = true,
        ReadOnly = false
    }.ConnectionString; // data source=test.sqlite;pooling=True;read only=False
File.Delete(DbFilePath);
using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString))
using (SQLiteCommand cmd = new SQLiteCommand("CREATE TABLE items(id INTEGER NOT NULL PRIMARY KEY)", conn))
{
    conn.Open();
    cmd.ExecuteNonQuery();
}
while (true) // <= if we comment the loop, the program executes without error
{
    using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString))
    using (SQLiteCommand cmd = new SQLiteCommand("INSERT INTO items(id) VALUES (1)", conn))
    {
        conn.Open();
        cmd.ExecuteNonQuery();
    }
    using (SQLiteConnection conn = new SQLiteConnection(readOnlyConnectionString))
    using (SQLiteCommand cmd = new SQLiteCommand("SELECT COUNT(*) FROM items", conn))
    {
        conn.Open();
        cmd.ExecuteScalar();
    }
    using (SQLiteConnection conn = new SQLiteConnection(readWriteConnectionString))
    using (SQLiteCommand cmd = new SQLiteCommand("DELETE FROM items", conn))
    {
        conn.Open();
        cmd.ExecuteNonQuery();
    }
}

你是否查看了这些连接字符串以确保它们实际上是不同的?这个文件在磁盘上是否可写? - Lasse V. Karlsen
@LasseV.Karlsen 连接字符串不同。文件可读写。正如我所说,如果我禁用连接池,或者我引入人为延迟 - 程序可以正常工作。 - Ulugbek Umirov
这看起来像是SQLiteConnectionPool类中的一个错误、设计缺陷,或者甚至是设计决策。从对源代码(SQLiteConnectionPool)的粗略扫描来看,它似乎只考虑文件名,而不是整个连接字符串。 - Lasse V. Karlsen
@LasseV.Karlsen 感谢您指出。的确,它似乎只关心文件名。 - Ulugbek Umirov
1个回答

5
SQLiteConnectionPool类的源代码来看,它在管理池条目时只考虑文件名。
以下是一个简短的摘录:
internal static void Add(
    string fileName,
    SQLiteConnectionHandle handle,
    int version

没有提到使用的连接字符串,只有文件名。因此,在需要不同的连接字符串以不同方式运作且分开池化时,使用内置的连接池机制将无法实现。
现在,“连接池”作为一个概念,是ADO.NET类层次结构的实现者自己决定的。SqlConnection类按唯一的连接字符串进行池化,但这不是IDbConnection实现必须遵循的规定。
因此,这可能只是System.Data.SQLite创建者所做的设计决策。然而,我建议给他们发送电子邮件并问问是否有意这样做。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接