Understand SQLite's locking model, its transactions, and the isolation anomalies described by the SQL standard.
Open this lesson in KodokonSQLite locks at the level of the entire database file, not the row. In the default mode (rollback journal), readers share a SHARED lock, but only one writer at a time can obtain the EXCLUSIVE lock. Consequence: writes are serialized. Let's create a table to work with transactions.
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;The lock evolves in stages: UNLOCKED, then SHARED (read), RESERVED (intent to write), PENDING, and finally EXCLUSIVE (write). If a transaction already holds the write lock, another receives the SQLITE_BUSY error. Rather than failing immediately, set a wait delay with busy_timeout, and enable WAL mode to let readers and the writer progress in parallel.
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;
-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;The SQL standard describes three anomalies: the dirty read (reading uncommitted data), the non-repeatable read (re-reading a row and seeing a different value), and the phantom read (replaying a range query and seeing new rows appear). Because SQLite serializes writers, it effectively behaves as SERIALIZABLE: these anomalies don't appear (outside shared-cache mode with read_uncommitted). Elsewhere, you choose the level explicitly.
-- 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 and BEGIN IMMEDIATE?