Безопасно ли SQLite3 обрабатывать параллельный доступ несколькими процессами чтение/запись из той же БД? Есть ли какие-либо исключения для платформы?
Параллельный доступ SQLite
Ответ 1
Если большинство этих параллельных доступов читаются (например, SELECT), SQLite может справиться с ними очень хорошо. Но если вы начнете писать одновременно, блокирование конкуренции может стать проблемой. Многое будет тогда зависеть от того, насколько быстро ваша файловая система, поскольку сам движок SQLite чрезвычайно быстр и имеет множество умных оптимизаций для минимизации конкуренции. Особенно SQLite 3.
Для большинства приложений для настольных компьютеров и ноутбуков/планшетов/телефонов SQLite достаточно быстр, так как не хватает concurrency. (Firefox широко использует SQLite для закладок, истории и т.д.)
Для серверных приложений кто-то некоторое время назад сказал, что в типичных сценариях (например, в блогах, форумах) можно отлично обрабатывать просмотры страниц размером менее 100 тыс. дней в день, и я еще не видел никаких доказательств обратного, Фактически, с современными дисками и процессорами 95% веб-сайтов и веб-сервисов будут работать отлично с SQLite.
Если вам нужен действительно быстрый доступ для чтения/записи, используйте базу данных SQLite в памяти. RAM на несколько порядков быстрее, чем диск.
Ответ 2
Да, SQLite хорошо справляется с параллелизмом, но он не лучший с точки зрения производительности. Из того, что я могу сказать, нет никаких исключений. Подробности на сайте SQLite: https://www.sqlite.org/lockingv3.html
Это утверждение представляет интерес: "Модуль пейджера следит за тем, чтобы изменения происходили одновременно, что либо происходят все изменения, либо ни один из них не происходит, чтобы два или более процесса не пытались одновременно обращаться к базе данных несовместимыми способами"
Ответ 3
Никто, кажется, не упомянул режим WAL (Write Ahead Log). Убедитесь, что транзакции организованы правильно и с установленным режимом WAL, нет необходимости блокировать базу данных, пока люди читают вещи, пока происходит обновление.
Единственная проблема заключается в том, что в какой-то момент WAL необходимо повторно включить в основную базу данных, и это происходит, когда последнее соединение с базой данных закрывается. С очень загруженным сайтом вам может потребоваться несколько секунд, чтобы все соединения были закрыты, но 100K обращений в день не должно быть проблемой.
Ответ 4
Да, это так. Давайте выясним, почему
SQLite является транзакционным
Все изменения в одной транзакции в SQLite происходят полностью или не происходят вообще
Такая поддержка ACID, а также одновременное чтение/запись предоставляются двумя способами - с использованием так называемого ведения журнала (давайте назовем это "старым способом") или ведения записи с опережением записи (давайте назовем это "новым способом")
Журналирование (Старый Путь)
В этом режиме SQLite использует блокировку DATABASE-LEVEL. Это важный момент для понимания.
Это означает, что всякий раз, когда ему нужно что-то прочитать/записать, он сначала получает блокировку на ВЕСЬ файл базы данных. Несколько читателей могут сосуществовать и читать что-то параллельно
Во время записи он гарантирует, что получена исключительная блокировка, и никакой другой процесс не читает/записывает одновременно, и, следовательно, запись безопасна.
Вот почему здесь говорят, что SQlite реализует сериализуемые транзакции
Неприятности
Поскольку необходимо каждый раз блокировать всю базу данных, и все ожидают, что процесс, обрабатывающий запись, страдает от параллелизма, и такие параллельные операции записи/чтения имеют довольно низкую производительность.
Откаты/отключения
Перед записью чего-либо в файл базы данных SQLite сначала сохраняет чанк, подлежащий изменению, во временный файл. Если что-то падает во время записи в файл базы данных, он подхватывает этот временный файл и отменяет изменения из него.
Запись с записью вперед или WAL (новый способ)
В этом случае все записи добавляются во временный файл (журнал предварительной записи), и этот файл периодически объединяется с исходной базой данных. Когда SQLite что-то ищет, он сначала проверит этот временный файл, а если ничего не будет найдено, перейдите к основному файлу базы данных.
В результате читатели не соревнуются с писателями, и их производительность намного лучше, чем у Old Way.
Предостережения
SQlite сильно зависит от базовой функциональности блокировки файловой системы, поэтому его следует использовать с осторожностью, более подробно здесь
Вы также можете столкнуться с ошибкой заблокированной базы данных, особенно в журнальном режиме, поэтому ваше приложение должно быть спроектировано с учетом этой ошибки.
Ответ 5
В 2019 году появилось два новых варианта одновременной записи, которые еще не выпущены, но доступны в отдельных ветках.
Преимущество этого режима журнала по сравнению с обычным режимом "wal" заключается в том, что авторы могут продолжать запись в один файл wal, в то время как другой проверен.
BEGIN CONCURRENT - ссылка на подробный документ
Усовершенствование BEGIN CONCURRENT позволяет нескольким авторам обрабатывать транзакции записи одновременно, если база данных находится в режиме "wal" или "wal2", хотя система все еще сериализует команды COMMIT.
Когда транзакция записи открывается с "BEGIN CONCURRENT", фактическая блокировка базы данных откладывается до тех пор, пока не будет выполнен COMMIT. Это означает, что любое количество транзакций, запущенных с BEGIN CONCURRENT, может выполняться одновременно. Система использует оптимистическую блокировку на уровне страниц, чтобы предотвратить фиксацию конфликтующих параллельных транзакций.
Вместе они присутствуют в begin-concurrent-wal2 или в каждой отдельной отдельной ветки.
Ответ 6
SQLite поддерживает неограниченное количество одновременных читателей, но в любой момент времени может работать только один писатель. Для многих ситуаций это не проблема. Писатель в очереди. Каждое приложение выполняет свою работу с базой данных быстро и движется дальше, и никакая блокировка не длится более нескольких десятков миллисекунд. Но есть некоторые приложения, которые требуют большего параллелизма, и этим приложениям, возможно, придется искать другое решение.
Это ясно в DOC.
В обработке транзакций SQLite реализует независимую обработку транзакций через эксклюзивные и разделяемые блокировки на уровне базы данных. И именно поэтому несколько процессов могут считывать данные из одной и той же базы данных одновременно, но только один может записывать в базу данных.
Исключительная блокировка должна быть получена до того, как процесс или поток захотят выполнить операцию записи в базу данных. После получения исключительной блокировки другие операции чтения или записи больше не будут выполняться.
Детали реализации для случая двух записей:
В SQLite есть таблица блокировок, которая помогает различным попыткам записи в базу данных блокировать ее, и это делается в последний момент для обеспечения максимального параллелизма.
Исходное состояние "UNLOCKED", и в этом состоянии соединение еще не обращалось к базе данных. Когда процесс подключен к базе данных и даже транзакция была запущена с BEGIN, соединение все еще находится в состоянии "UNLOCKED".
Следующее состояние разблокированного состояния - это состояние SHARED. Чтобы иметь возможность считывать (не записывать) данные из базы данных, соединение должно сначала войти в состояние SHARED, то есть сначала получить блокировку SHARED. Несколько соединений могут одновременно получать и поддерживать блокировки SHARED, то есть несколько соединений могут одновременно считывать данные из одной и той же базы данных. Но даже если блокировка SHARED не была снята, она не позволяет никакому соединению записывать базу данных.
Если соединение хочет записать базу данных, оно должно сначала получить зарезервированную блокировку.
Одновременно может быть активна только одна зарезервированная блокировка, хотя несколько блокировок SHARED могут сосуществовать с одной зарезервированной блокировкой. RESERVED отличается от PENDING тем, что новые блокировки SHARED могут быть получены, когда есть блокировка RESERVED.
Как только соединение получает зарезервированную блокировку, оно может начать обработку операций модификации базы данных, хотя эти изменения могут быть сделаны только в буфере, а не фактически записаны на диск. Изменения, внесенные в содержимое считывания, сохраняются в буфере памяти. Когда соединение хочет отправить модификацию (или транзакцию), необходимо обновить зарезервированную блокировку до эксклюзивной блокировки. Чтобы получить блокировку, вы должны сначала снять блокировку до ожидающей блокировки.
Блокировка PENDING означает, что процесс, удерживающий блокировку, хочет выполнить запись в базу данных как можно скорее и просто ожидает от всех текущих блокировок SHARED, чтобы снять ее, чтобы получить блокировку EXCLUSIVE. Новые блокировки SHARED не разрешены для базы данных, если активна блокировка PENDING, хотя существующие блокировки SHARED могут продолжаться.
ЭКСКЛЮЗИВНАЯ блокировка необходима для записи в файл базы данных. Только одна ИСКЛЮЧИТЕЛЬНАЯ блокировка разрешена для файла, и никакие другие блокировки любого рода не могут сосуществовать с ИСКЛЮЧИТЕЛЬНОЙ блокировкой. Чтобы максимизировать параллелизм, SQLite работает, чтобы минимизировать время, в течение которого удерживаются ИСКЛЮЧИТЕЛЬНЫЕ блокировки.
Таким образом, вы можете сказать, что SQLite безопасно обрабатывает одновременный доступ нескольких процессов, записывающих в один и тот же БД, просто потому, что он не поддерживает его! Вы получите SQLITE_BUSY
или SQLITE_LOCKED
для второго писателя, когда он достигнет ограничения повторных попыток.
Ответ 7
Эта ветка устарела, но я думаю, было бы хорошо поделиться результатами моих тестов, выполненных на sqlite: Я запускал 2 экземпляра программы python (разные процессы в одной и той же программе), выполняя инструкции SELECT и UPDATE SQL-команды в транзакции с EXCLUSIVE блокировкой и таймаутом, установленными на 10 секунд, чтобы получить блокировку, и результат был разочаровывающим. Каждый экземпляр делал в шаге 10000 шагов:
- подключиться к db с эксклюзивной блокировкой
- выберите одну строку для чтения счетчика.
- обновить строку с новым значением, равным счетчику, увеличенному на 1
- закрыть соединение с db
Даже если sqlite предоставил эксклюзивную блокировку транзакции, общее количество реально выполненных циклов не было равно 20 000, но меньше (общее количество итераций по одному счетчику, подсчитанное для обоих процессов). Программа Python почти не выбрасывает ни одного исключения (только один раз при выборе для 20 исполнений). Обновление sqlite в момент теста было 3.6.20 и python v3.3 CentOS 6.5. По моему мнению, лучше найти более надежный продукт для такого рода работ или ограничить запись в sqlite единственным уникальным процессом/потоком.