SQLite Параллельный доступ

177

Безопасно ли обрабатывает SQLite3 одновременный доступ нескольких процессов чтения / записи из одной и той же БД? Есть ли какие-либо исключения для платформы?

Ананд
источник
3
Я забыл упомянуть о награде за награду: большинство ответов говорят, что все в порядке: «SQLite достаточно быстр», «SQLite хорошо справляется с параллелизмом» и т. Д., Но, imho, не отвечает подробно / не объясняет, что происходит, если две операции записи прибудет в одно и то же время (теоретический случай очень редок). 1) Будет ли это вызвать ошибку и прервать программу? или 2) будет ли вторая операция записи ждать до завершения первой? или 3) Будет ли отброшена одна из операций записи (потеря данных!)? 4) Что-то еще? Знание ограничений одновременной записи может быть полезно во многих ситуациях.
Basj
7
@Basj Короче говоря, 2) он будет ждать и повторять несколько раз (настраивается), 1) вызвать ошибку, SQLITE_BUSY.3) вы можете зарегистрировать обратный вызов для обработки ошибок SQLITE_BUSY.
августа

Ответы:

112

Если большинство из этих одновременных обращений - чтение (например, SELECT), SQLite может справиться с ними очень хорошо. Но если вы начнете писать одновременно, конфликт блокировки может стать проблемой. Многое будет зависеть от скорости вашей файловой системы, так как сам механизм SQLite чрезвычайно быстр и имеет много умных оптимизаций для минимизации конфликтов. Особенно SQLite 3.

Для большинства приложений для настольных ПК / ноутбуков / планшетов / телефонов SQLite достаточно быстр, поскольку не хватает параллелизма. (Firefox широко использует SQLite для закладок, истории и т. Д.)

Что касается серверных приложений, кто-то некоторое время назад сказал, что все, что меньше 100К просмотров страниц в день, может отлично обрабатываться базой данных SQLite в типичных сценариях (например, в блогах, на форумах), и я пока не вижу никаких доказательств обратного. Фактически, с современными дисками и процессорами, 95% веб-сайтов и веб-сервисов будут прекрасно работать с SQLite.

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

kijin
источник
16
ОП спрашивает не об эффективности и скорости, а о параллельном доступе. Веб-серверы не имеют к этому никакого отношения. То же самое в базе данных памяти.
Ярекчек
1
Вы правы в некоторой степени, но эффективность / скорость действительно играет роль. Более быстрый доступ означает, что время ожидания блокировок меньше, что снижает недостатки производительности параллелизма SQLite. В частности, если у вас мало и быстрых записей, БД не будет иметь никаких проблем с параллелизмом для пользователя.
Caboose
1
Как бы вы управляли одновременным доступом к базе данных sqlite в памяти?
P-Gn
42

Да, SQLite хорошо справляется с параллелизмом, но он не самый лучший с точки зрения производительности. Из того, что я могу сказать, нет никаких исключений. Подробности на сайте SQLite: https://www.sqlite.org/lockingv3.html

Это утверждение представляет интерес: «Модуль пейджера следит за тем, чтобы изменения происходили одновременно, чтобы все изменения произошли или ни один из них не произошел, чтобы два или более процесса не пытались одновременно обращаться к базе данных несовместимыми способами»

vcsjones
источник
2
Вот несколько комментариев о проблемах на разных платформах , а именно в файловых системах NFS и Windows (хотя это может относиться только к старым версиям Windows ...)
конец
1
Можно ли загрузить базу данных SQLite3 в оперативную память, которая будет использоваться для всех пользователей в PHP? Я думаю, нет, потому что это процедурно
Anon343224user
1
@foxyfennec .. отправная точка, хотя SQLite не может быть оптимальной базой данных для этого варианта использования. sqlite.org/inmemorydb.html
kingPuppy
37

Да, это так. Давайте выясним, почему

SQLite является транзакционным

Все изменения в пределах одной транзакции в SQLite происходят полностью или не происходят вообще

Такая поддержка ACID, а также одновременное чтение / запись предоставляются двумя способами - с использованием так называемого ведения журнала (давайте назовем это « старым способом ») или ведения записи с опережением записи (давайте назовем это « новым способом »)

Журналирование (Старый путь)

В этом режиме SQLite использует блокировку DATABASE-LEVEL . Это важный момент для понимания.

Это означает, что всякий раз, когда ему нужно что-то прочитать / записать, он сначала получает блокировку на ВСЁ файле базы данных. Несколько читателей могут сосуществовать и читать что-то параллельно

Во время записи он гарантирует, что получена исключительная блокировка, и никакой другой процесс не читает / пишет одновременно, и, следовательно, запись безопасна.

Вот почему здесь говорится, что SQlite реализует сериализуемые транзакции

Неприятности

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

Откаты / отключения

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

Запись с записью вперед или WAL (новый способ)

В этом случае все записи добавляются во временный файл ( журнал предварительной записи ), и этот файл периодически объединяется с исходной базой данных. Когда SQLite что-то ищет, он сначала проверит этот временный файл, а если ничего не будет найдено, перейдите к основному файлу базы данных.

В результате, читатели не конкурируют с писателями, и производительность намного лучше по сравнению со старым путем.

Предостережения

SQlite сильно зависит от базовой функциональности блокировки файловой системы, поэтому его следует использовать с осторожностью, более подробно здесь

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

ffeast
источник
35

Никто, кажется, не упомянул режим WAL (Write Ahead Log). Убедитесь, что транзакции правильно организованы и при включенном режиме WAL нет необходимости держать базу данных заблокированной, пока люди читают что-то во время обновления.

Единственная проблема заключается в том, что в какой-то момент WAL необходимо повторно включить в основную базу данных, и он делает это, когда закрывается последнее соединение с базой данных. На очень загруженном сайте вы можете обнаружить, что все соединения закрываются за несколько секунд, но 100 000 обращений в день не должно быть проблемой.

akc42
источник
Интересно, но работает только на одной машине, а не на сценариях, где база данных доступна по всей сети.
Бобик,
Стоит отметить, что время ожидания по умолчанию для писателя составляет 5 секунд, и после этого database is lockedошибка будет
поднята
16

В 2019 году появилось два новых варианта одновременной записи, которые еще не выпущены, но доступны в отдельных ветках.

"PRAGMA journal_mode = wal2"

Преимущество этого режима журнала по сравнению с обычным режимом "wal" состоит в том, что авторы могут продолжать запись в один файл wal, в то время как другой является контрольной точкой.

BEGIN CONCURRENT - ссылка на подробный документ

Усовершенствование BEGIN CONCURRENT позволяет нескольким авторам обрабатывать транзакции записи одновременно, если база данных находится в режиме «wal» или «wal2», хотя система все еще сериализует команды COMMIT.

Когда транзакция записи открывается с «BEGIN CONCURRENT», фактическая блокировка базы данных откладывается до тех пор, пока не будет выполнен COMMIT. Это означает, что любое количество транзакций, запущенных с BEGIN CONCURRENT, может выполняться одновременно. Система использует оптимистическую блокировку на уровне страниц, чтобы предотвратить фиксацию конфликтующих параллельных транзакций.

Вместе они присутствуют в begin-concurrent-wal2 или каждый в отдельной собственной ветке .

VB
источник
1
Есть ли у нас какие-либо идеи, когда эти функции войдут в релизную версию? Они действительно могут пригодиться для меня.
Питер Мур
2
Без понятия. Вы могли бы легко построить из веток. Для .NET у меня есть библиотека с низкоуровневым интерфейсом и WAL2 + одновременное начало + FTS5: github.com/Spreads/Spreads.SQLite
VB
О, конечно, спасибо. Меня больше интересует стабильность. SQLite - высший класс, когда дело доходит до их релизов, но я не знаю, насколько рискованно было бы использовать ветку в рабочем коде.
Питер Мур
2
Смотрите эту ветку github.com/Expensify/Bedrock/issues/65 и Bedrock в целом. Они используют его в производстве и продвигают это begin concurrent.
VB
13

SQLite имеет блокировку чтения-записи на уровне базы данных. Несколько соединений (возможно, принадлежащих разным процессам) могут считывать данные из одной и той же базы данных одновременно, но только одно может записывать в базу данных.

SQLite поддерживает неограниченное количество одновременных читателей, но в любой момент времени он может использовать только одного писателя. Для многих ситуаций это не проблема. Писатель в очереди. Каждое приложение выполняет свою работу с базой данных быстро и движется дальше, и никакая блокировка не длится более нескольких десятков миллисекунд. Но есть некоторые приложения, которые требуют большего параллелизма, и эти приложения, возможно, должны искать другое решение. - правильное использование для SQLite @ SQLite.org

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

Исключительная блокировка должна быть получена до того, как соединение выполнит операцию записи в базу данных. После получения эксклюзивной блокировки операции чтения и записи из других подключений блокируются до тех пор, пока блокировка не будет снята снова.

Детали реализации для случая одновременной записи

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

Исходное состояние UNLOCKED, и в этом состоянии соединение еще не обращалось к базе данных. Когда процесс подключен к базе данных и даже транзакция была запущена с BEGIN, соединение все еще находится в состоянии UNLOCKED.

После состояния UNLOCKED следующее состояние - это состояние SHARED. Чтобы иметь возможность читать (не записывать) данные из базы данных, соединение должно сначала войти в состояние SHARED, получив блокировку SHARED. Несколько соединений могут получать и поддерживать блокировки SHARED одновременно, поэтому несколько соединений могут одновременно считывать данные из одной и той же базы данных. Но до тех пор, пока не освобождена даже одна блокировка SHARED, ни одно соединение не сможет успешно завершить запись в базу данных.

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

Только одна зарезервированная блокировка может быть активной одновременно, хотя несколько блокировок SHARED могут сосуществовать с одной зарезервированной блокировкой. RESERVED отличается от PENDING тем, что новые замки SHARED могут быть получены при наличии зарезервированного замка. - Блокировка файлов и параллелизм в SQLite версии 3 @ SQLite.org

Как только соединение получает зарезервированную блокировку, оно может начать обработку операций модификации базы данных, хотя эти изменения могут быть сделаны только в буфере, а не фактически записаны на диск. Изменения, внесенные в содержимое считывания, сохраняются в буфере памяти. Когда соединение хочет отправить изменение (или транзакцию), необходимо обновить зарезервированную блокировку до ИСКЛЮЧИТЕЛЬНОЙ блокировки. Для того, чтобы получить замок, вы должны сначала поднять замок в ожидании блокировки.

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

ЭКСКЛЮЗИВНАЯ блокировка необходима для записи в файл базы данных. Для файла допускается только одна ИСКЛЮЧИТЕЛЬНАЯ блокировка, и никакие другие блокировки не могут сосуществовать с ИСКЛЮЧИТЕЛЬНОЙ блокировкой. Чтобы максимизировать параллелизм, SQLite работает, чтобы минимизировать время, в течение которого удерживаются ИСКЛЮЧИТЕЛЬНЫЕ блокировки. - Блокировка файлов и параллелизм в SQLite версии 3 @ SQLite.org

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

obgnaw
источник
Спасибо. Пример кода с двумя авторами был бы очень хорош, чтобы понять, как он работает.
Basj
2
@ Basj, короче говоря, sqlite имеет блокировку чтения-записи для файла базы данных. Это то же самое, что и одновременная запись файла. И с WAL, все еще не может одновременная запись, но WAL может ускорить запись, и чтение и запись могут быть одновременными. И WAL вводит новую блокировку, такую ​​как WAL_READ_LOCK, WAL_WRITE_LOCK.
obgnaw
2
Не могли бы вы использовать Очередь и иметь несколько потоков, подающих Очередь, и только один поток, записывающий в БД с помощью операторов SQL в Очереди. Что - то вроде этого
Gabriel
Для WAL - см. Sqlite.org/wal.html
Тодд
7

Этот поток старый, но я думаю, что было бы хорошо поделиться результатами моих тестов, выполненных на sqlite: я выполнил 2 экземпляра программы на python (разные процессы одной и той же программы), выполнив операторы SELECT и UPDATE sql в транзакциях с блокировкой EXCLUSIVE и таймаутом, установленным в 10 секунд, чтобы получить блокировку, и результат был разочаровывающим. Каждый экземпляр делал в шаге 10000:

  • подключиться к БД с эксклюзивным замком
  • выберите в одной строке, чтобы прочитать счетчик
  • обновить строку новым значением, равным счетчику, увеличенному на 1
  • тесная связь с БД

Даже если sqlite предоставил монопольную блокировку транзакции, общее количество реально выполненных циклов было не равно 20 000, а меньше (общее количество итераций по одному счетчику для обоих процессов). Программа на Python почти не выдает ни одного исключения (только один раз во время выбора для 20 выполнений). Редакция sqlite на момент тестирования составляла 3.6.20, а python v3.3 - CentOS 6.5. По моему мнению, лучше найти более надежный продукт для такой работы или ограничить записи в sqlite одним уникальным процессом / потоком.

асф лас
источник
5
Похоже, вам нужно сказать несколько волшебных слов, чтобы получить блокировку в python, как обсуждалось здесь: stackoverflow.com/a/12848059/1048959 Это несмотря на то, что документация по python sqlite заставляет вас верить, что этого with conдостаточно.
Дэн Шталке,