Разберись в модели блокировок SQLite, в его транзакциях и в аномалиях изоляции, которые описывает стандарт SQL.
Открыть этот урок в KodokonSQLite блокирует на уровне всего файла базы данных, а не строки. В режиме по умолчанию (журнал отката) читатели делят блокировку SHARED, но получить блокировку EXCLUSIVE может только один писатель за раз. Следствие: записи сериализуются. Создадим таблицу, чтобы поработать с транзакциями.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
);
INSERT INTO accounts (id, balance)
VALUES (1, 100), (2, 50);
-- Read: deferred transaction (default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;Блокировка развивается ступенями: UNLOCKED, затем SHARED (чтение), RESERVED (намерение записать), PENDING и, наконец, EXCLUSIVE (запись). Если блокировку на запись уже держит одна транзакция, другая получает ошибку SQLITE_BUSY. Вместо того чтобы падать сразу, задай время ожидания через busy_timeout и включи режим WAL, чтобы читатели и писатель могли продвигаться параллельно.
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;
-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;Стандарт SQL описывает три аномалии: грязное чтение (чтение незафиксированных данных), неповторяющееся чтение (перечитываешь строку и видишь другое значение) и фантомное чтение (повторяешь запрос по диапазону и видишь, как появляются новые строки). Поскольку SQLite сериализует писателей, на практике он ведёт себя как SERIALIZABLE: эти аномалии не появляются (кроме режима общего кэша с read_uncommitted). В других движках уровень выбираешь явно.
-- Atomic transfer: immediate write lock
BEGIN IMMEDIATE;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
UPDATE accounts
SET balance = balance + 10
WHERE id = 2;
COMMIT;BEGIN и BEGIN IMMEDIATE?