Соединение SQLite в пуле только для чтения используется для доступа на чтение и запись.
Используемый пакет 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()
Если я отключу пул, то программа работает нормально. Я предполагаю, что каким-то образом объединенное соединение только для чтения используется для соединения чтения-записи. Я что-то пропускаю - то есть ожидаемое поведение или нет?
Минимальный код для воспроизведения (он не срабатывает при INSERT или DELETE). Если я введу задержку, например 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();
}
}
1 ответ
Исходя из исходного кода для класса SQLiteConnectionPool, похоже, он учитывает только имя файла при управлении записями пула.
Вот краткая выдержка:
internal static void Add(
string fileName,
SQLiteConnectionHandle handle,
int version
Там нет упоминания об используемой строке подключения, только имя файла. Таким образом, использование встроенного механизма пула соединений, когда вам нужно, чтобы разные строки соединения вели себя по-разному, а пул отдельно не будет работать.
Теперь "пул соединений" как концепция - это то, что решает сам разработчик иерархии классов ADO.NET. Классы SqlConnection объединяются для каждой уникальной строки подключения, но это никоим образом не требуется реализациями IDbConnection.
Таким образом, это может быть просто дизайнерское решение, принятое создателями System.Data.SQLite. Я бы, однако, отправил им электронное письмо и спросил, было ли это намеренно или нет.