Make your writes atomic with transactions and understand what the ACID guarantees mean in day-to-day practice.
Open this lesson in KodokonA bank transfer is two UPDATEs: debit one account, credit the other. If the second fails, the database lies. The transaction makes the block indivisible, with the ACID guarantees: Atomicity (all or nothing), Consistency (constraints stay valid), Isolation (concurrent transactions never see each other half-done), Durability (a COMMIT survives a crash). For this lesson, prefer the real sqlite3 client: some web sandboxes auto-commit every query.
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);The nominal case: BEGIN opens the transaction, the writes pile up, COMMIT makes them final all at once. In between, no other connection sees the intermediate state (alice debited, bruno not yet credited).
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;Now the failure case. Debiting bruno by 400 would violate CHECK (balance >= 0): the statement fails. A SQLite subtlety: by default, an error cancels the offending statement but leaves the transaction open. It is up to your application code to decide: ROLLBACK to undo everything (the sane reflex), or continue if the error is recoverable.
BEGIN;
UPDATE accounts
SET balance = balance - 400
WHERE owner = 'bruno';
-- Error: CHECK (balance >= 0) rejects the row.
ROLLBACK;
SELECT owner, balance FROM accounts;