Сделай записи атомарными с помощью транзакций и пойми, что гарантии ACID означают в повседневной практике.
Открыть этот урок в KodokonБанковский перевод - это два UPDATE: списать с одного счёта, зачислить на другой. Если второй упадёт, база данных начнёт врать. Транзакция делает такой блок неделимым, с гарантиями ACID: Atomicity, атомарность (всё или ничего), Consistency, согласованность (ограничения остаются в силе), Isolation, изоляция (параллельные транзакции никогда не видят друг друга наполовину выполненными), Durability, долговечность (COMMIT переживает сбой). Для этого урока лучше взять настоящий клиент sqlite3: некоторые веб-песочницы автоматически коммитят каждый запрос.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
balance REAL NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts (owner, balance)
VALUES
('alice', 500.0),
('bruno', 120.0);Штатный случай: BEGIN открывает транзакцию, записи накапливаются, COMMIT делает их окончательными разом. Между этими двумя моментами ни одно другое соединение не видит промежуточное состояние (у alice уже списано, а bruno ещё не зачислено).
BEGIN;
UPDATE accounts
SET balance = balance - 200
WHERE owner = 'alice';
UPDATE accounts
SET balance = balance + 200
WHERE owner = 'bruno';
COMMIT;
SELECT owner, balance FROM accounts;Теперь случай сбоя. Списание 400 у bruno нарушило бы CHECK (balance >= 0): инструкция падает. Тонкость SQLite: по умолчанию ошибка отменяет проблемную инструкцию, но оставляет транзакцию открытой. Решать твоему коду приложения: ROLLBACK, чтобы откатить всё (здравый рефлекс), или продолжить, если ошибка поправима.
BEGIN;
UPDATE accounts
SET balance = balance - 400
WHERE owner = 'bruno';
-- Error: CHECK (balance >= 0) rejects the row.
ROLLBACK;
SELECT owner, balance FROM accounts;