База SQLite. Работа в многопоточном приложении .NET

Ваш отзыв

sqlite db

Добрый вечер! Сегодня многие десктопные приложения так или иначе хранят свои данные в базе. Самые известные примеры: база SQLite для Skype, база собранных ключевиков для программы «Магадан». Последнее приложение написано на .net, как легко видеть после установки. В процессе работы с SQLite обострилась проблема доступа к базе из различных потоков. Вот тут ждал интересный сюрприз, который с одной стороны много где описан в «интернетах», как явление, но мало где дается решение. Если мы создадим многопоточное приложение и «в лоб» будем из одного потока что-то читать, а из другого писать, мы с высокой долей вероятности однажды получим ошибку «database is locked», и ничего не будет работать.

Неоднократно читала, что sqlite создана для однопоточных приложений, в многопоточных она работать отказывается и бла-бла-бла. Во-первых, переходим к документации и обнаруживаем, что, оказывается, данная СУБД умеет работать аж в трех режимах:

  • Single-thread
  • Multi-thread
  • Serialized

Чуть ниже в документации написано, что режим выбирается либо в момент компиляции библиотеки СУБД, либо в момент инициализации приложения. использующего базу SQLite.

Режим Single-Thread — это режим, при котором нормальная работа с БД возможна только из одного потока приложения. При этом отключаются все объекты синхронизации в библиотеке (мьютексы в данном случае).

В режиме Multi-thread обеспечивается потокобезопасная работа с БД при условии, что ни один из потоков не будет использовать БД в монопольном режиме.

Режим Serialized делает тоже самое, что и предыдущий режим без ограничений.

Во-вторых, в документации написано, что режим по умолчанию Serialized (sic!). Стало быть, работа с многопоточными приложениями как бы подразумевается. Но давайте не будем слепо верить документации, а скачаем исходники SQLite для .NET. Среди проектов нас интересует сейчас тот, который называется SQLite.Interop.2013, а если точнее, то файл внутри него sqlite3.props. Открываем его и видим, с какими директивами была собрана библиотека. Среди всего прочего есть SQLITE_THREADSAFE=1. Смотрим в документации определение и…. БИНГО! Это означает, что действительно используется режим Serialized. Кстати, в той же документации написано, что это утверждение также было бы верно, если данная директива вообще отсутствовала.

Чтобы посмотреть, как ведет себя база данных SQLite при работе с ней из многопоточного приложения, «на коленке» было написано это самое многопоточное тестовое приложение  с небольшой тестовой базой данных testbase.db.  Его исходники можно скачать ЗДЕСЬ. Само приложение скачивается по ссылке с уникальным анкором — скачать .

sqlite wal

После запуска приложения можно сразу нажать на ENTER или кликнуть по кнопке «Запуск теста». Через пару томительных мгновений в белом поле под кнопкой будут появляться сообщения «!!!!!!!!!! database is lockeddatabase is locked», которые, впрочем, через некоторое время сменятся на «Комментарий успешно добавлен».

sqlite journal wal

В бэкграунде происходит следующее: некий SELECT из одного потока надолго присасывается к базе, долго держит с ней коннект, а из другого потока идут многочисленные попытки INSERT’а, которые через некоторое время увенчаются успехом, а точнее ровным счетом тогда, когда «отвалится» SELECT.

Нажимаем на кнопку «Стоп», очищаем листвью комбинацией клавиш Ctrl + X или при помощи самой правой маленькой кнопки с красным крестиком и ставим галочку «WAL». Снова нажимаем на ENTER или кликаем по кнопке «Запуск теста». Тут все совсем по-другому:

sqlite multithreading

Почему-то INSERT тут же начал отрабатывать по-нормальному.

Разница состоит в том, что в первом случае мы собирали connection string к БД без явного указания, как работать с журналом транзакций, т.е. делали так:

И на этом останавливались.

А во втором случае в методе, в котором читаются данные из БД, мы добавили строки:

Последняя из не рассмотренных кнопок — это кнопка «Очистить базу». Она не только удаляет ту таблицу, которая наполняется во время теста, но и реально сжимает файл БД. Дело в том, что при простом удалении данным командой «DELETE from Comments» строки-то удаляются, а вот место их расположения никуда не девается, а зануляется, то есть забивается значениями ASCII 00. Чтобы очистить эти ненужные блоки, есть команда «VACUUM» целиком для БД или «VACUUM Table» для отдельной таблицы Table.

Что же это такое — WAL? Исходя из документации это расшифровывается как Write-Ahead Log. При использовании данной опции «читатели» больше не блокируют «писателей» и наоборот. Смысл состоит в том, что оригинальный контент БД остается в самом файле БД, а все изменения записываются в особый файл  с расширением db-wal. Затем начинается процесс слияния данных — чекпоинт. Это может происходить автоматически после добавления в wal-файл 1000 записей. По умолчанию приложение, использующее SQLite, не следит за возникновением чекпоинтов, однако такая возможность есть, например, можно отключить автоматические чекпоинты и включить «ручной режим» . Следует учитывать, что процесс чекпоинта останавливается, если он видит, что страницы БД, которые ему предстоит обновить, кто-то читает, или есть незакрытая транзакция.  Отсюда вполне возможен рост файлов wal.

ОДИН ВАЖНЫЙ МОМЕНТ!!!

Сточки, указанные выше

добавлены не просто так. Если их убрать, то перезапуск приложения НЕ приведет к сбросу режима WAL. Этот параметр persistent. Единственный вариант — это удалить файл testbase.db и восстановить его из какого-нибудь бэкапа, например, закачать программу отсюда заново. Почему именно Delete? Взято из документации, раздел Backwards Compatibility  .


Оставьте комментарий

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url=""> <img src="" alt="" class="" width="" height="">

http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ab.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ac.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ag.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ah.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ai.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ak.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/am.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/an.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ao.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/aq.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ar.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/at.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/av.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/aw.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/ay.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/az.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/bb.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/bc.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/bd.gif 
http://noservice.ru/wp-content/plugins/wp-monalisa/icons/be.gif 
больше...