Как выполнить запрос SQLite с устройством чтения данных без блокировки базы данных?

Я использую System.Data.Sqlite для доступа к базе данных SQLite на С#. У меня есть запрос, который должен читать строки в таблице. При повторении строк и пока читатель открыт, необходимо выполнить определенные обновления SQL. Я запускаю исключение "база данных заблокировано".

В документации SQLite указано:

Когда процесс хочет прочитать из файла базы данных, он выполнил следующую последовательность шагов:

  • Откройте файл базы данных и получите блокировку SHARED.

В документации далее говорится о блокировке "SHARED":

База данных может быть прочитана, но не записана. Любое количество процессов может удерживать блокировки SHARED одновременно, поэтому может быть много одновременных считывателей. Но никакому другому потоку или процессу не разрешено записывать в файл базы данных, пока активна одна или несколько блокировок SHARED.

В FAQ указано:

Несколько процессов могут одновременно открывать одну и ту же базу данных. Несколько процессов могут одновременно выполнять SELECT. Однако только один процесс может вносить изменения в базу данных в любой момент времени.

В книге Окончательное руководство по SQLite говорится:

... соединение может выбрать наличие уровня изоляции без чтения, используя read_uncommited прагма. Если для него установлено значение true, тогда соединение не будет помещать блокировки чтения в таблицы, которые он читает. Поэтому другой писатель может фактически изменить таблицу, поскольку соединение в режиме чтения-незамкнутого не может блокировать и не блокировать какие-либо другие соединения.

Я попытался установить прагму для чтения без фиксации в инструкции команды запроса SQL следующим образом:

PRAGMA read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

Обновление SQL в том же потоке с использованием другого соединения по-прежнему не удалось с исключением "база данных заблокирована". Затем я попытался установить уровень изоляции, который будет считаться незафиксированным в экземпляре соединения. Все равно никаких изменений с тем же исключением.

Как я могу сделать, чтобы открытое устройство чтения данных перебирало строки в базе данных без блокировки базы данных, чтобы я мог выполнять обновления?

Update:

Оба ответа ниже работают. Однако я отказался от использования журнала отката по умолчанию в настоящее время, используя запись Write-Ahead Logging, которая обеспечивает улучшенные concurrency чтения и записи базы данных.

Ответ 1

Используйте WAL.

Ответ 2

Мне не удалось заставить это работать с использованием поставщика данных с открытым исходным кодом из здесь. Тем не менее, мне удалось заставить это работать, используя бесплатную стандартную версию dotConnect следующим образом:

Создайте приведенный ниже DLL-импорт, чтобы мы могли включить общий кеш для SQLite.

[DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int sqlite3_enable_shared_cache(int enable);

Выполните вышеуказанную функцию, чтобы включить общий кеш. Обратите внимание: это нужно выполнить только один раз для всего процесса - см. Документация по SQLite.

sqlite3_enable_shared_cache(1);

Затем префикс инструкции SQL-запроса, используемой считывателем данных, с инструкцией pragma следующим образом:

PRAGMA read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

Теперь можно свободно обновлять и вставлять строки во время чтения данных. Дополнительную документацию по SQLite в общем кэше можно найти здесь.

Update:

Новая версия поставщика данных Devite SQLite теперь поддерживает это улучшенным образом. Чтобы включить общий кэш, вы можете сделать следующий вызов:

Devart.Data.SQLite.SQLiteConnection.EnableSharedCache();

Можно сконфигурировать незафиксированные чтения в строку соединения, например, следующим образом:

Devart.Data.SQLite.SQLiteConnectionStringBuilder builder = new SQLiteConnectionStringBuilder();
builder.ReadUncommitted = true;
builder.DateTimeFormat = Devart.Data.SQLite.SQLiteDateFormats.Ticks;
builder.DataSource = DatabaseFilePath;
builder.DefaultCommandTimeout = 300;
builder.MinPoolSize = 0;
builder.MaxPoolSize = 100;
builder.Pooling = true;
builder.FailIfMissing = false;
builder.LegacyFileFormat = false;
builder.JournalMode = JournalMode.Default;
string connectionString = builder.ToString();