sqlite3.OperationalError: database is locked
It shows up the moment a second process, thread or background job writes to the same SQLite file. Often it appears even after you've set a timeout, and even with WAL mode enabled. Here's why, and the settings that actually fix it.
The one rule: a single writer
SQLite allows many readers but only one writer at a time, for the whole database file. SQLITE_BUSY ("database is locked") means your connection needed a lock that another connection holds, and it gave up.
Two defaults make this much worse than it needs to be.
Problem 1: the busy timeout defaults to zero
Out of the box, SQLite doesn't wait at all. If the lock is taken, it fails immediately. Set a timeout on every connection:
PRAGMA busy_timeout = 5000; -- wait up to 5 s for the lock
sqlite3.connect("app.db", timeout=5.0) # Python sets busy_timeout for you
Many language bindings don't set one. Check yours.
Problem 2: deferred transactions can't wait
This is why you still get errors with a timeout. BEGIN starts a DEFERRED transaction: it takes no lock until the first read (shared lock), then tries to upgrade to a write lock on the first write.
BEGIN; -- deferred
SELECT balance FROM accounts WHERE id=1; -- read snapshot taken
UPDATE accounts SET balance ...;
