Как выполнить SQLite запрос с помощью считывателя данных без блокировки базы данных? - PullRequest
11 голосов
/ 20 марта 2011

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

Документация SQLite гласит:

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

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

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

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

В FAQ говорится:

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

Книга Полное руководство по SQLite гласит:

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

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

PRAGMA read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

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

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

Обновление:

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

Ответы [ 2 ]

8 голосов
/ 20 марта 2011

Использование режима WAL .

2 голосов
/ 22 марта 2011

Мне не удалось заставить это работать, используя поставщика данных с открытым исходным кодом из здесь . Тем не менее, я смог заставить это работать, используя бесплатную стандартную версию 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 read_uncommitted = 1;
SELECT Column1, Column2 FROM MyTable

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

Обновление:

Более новая версия поставщика данных Devart 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();
...