Transactions and performance
Use transactions
Group related changes so SQLite commits all of them or none of them.
8 minute lesson
Start a transaction before changes that must succeed together:
BEGIN;
UPDATE accounts SET balance = balance - 20 WHERE id = 1;
UPDATE accounts SET balance = balance + 20 WHERE id = 2;
COMMIT;
Without the transaction, a failure after the first UPDATE removes money from one account without adding it to the other. The transaction makes the pair one unit: either COMMIT makes both changes durable, or ROLLBACK discards both.
Applications should roll back when any statement fails:
db.exec('BEGIN')
try {
debit.run(20, 1)
credit.run(20, 2)
db.exec('COMMIT')
} catch (error) {
db.exec('ROLLBACK')
throw error
}
SQLite starts an implicit transaction for a statement when you do not start one explicitly. An explicit transaction matters when several statements share one business outcome, or when a batch of inserts should avoid the overhead of committing each row separately.
BEGIN is deferred by default: SQLite does not acquire a write lock until the first write. BEGIN IMMEDIATE attempts to start the write transaction immediately. That can be useful when you prefer to discover writer contention before doing preparatory work.
SQLite allows multiple readers, but only one writer at a time. Do not open a transaction, call a remote API, wait for user input, and then commit. That holds the write transaction much longer than necessary and makes other writers wait or fail with SQLITE_BUSY.
Keep the transaction around database work only. Compute and validate first, begin, perform the related statements, commit, then do slow external work.
Try it: add a constraint that makes the second transfer update fail. Confirm that your error path rolls back and the first balance remains unchanged.
Lesson completed