SQLite keeps a whole database in one file and lets many connections read at once but only one write at a time. When a second connection tries to write while another holds the write lock, it waits for a timeout and then fails with database is locked.
Reproduce it
Connection a starts writing and does not commit. Connection b tries to write to the same file.
import os
import sqlite3
import tempfile
path = os.path.join(tempfile.mkdtemp(), "t.db")
a = sqlite3.connect(path, timeout=0.2)
a.execute("CREATE TABLE t (x INTEGER)")
a.commit()
a.execute("BEGIN IMMEDIATE")
a.execute("INSERT INTO t VALUES (1)") # a holds the write lock and has not committed
b = sqlite3.connect(path, timeout=0.2)
try:
b.execute("INSERT INTO t VALUES (2)")
except sqlite3.OperationalError as error:
print("b:", error)
a.commit() # a lets go
b.execute("INSERT INTO t VALUES (2)")
b.commit()
print("after a commits:", b.execute("SELECT COUNT(*) FROM t").fetchone())
b: database is locked
after a commits: (2,)
Nothing is broken. b simply gave up waiting before a finished. As soon as a committed, b succeeded. So the cure comes down to one of two things: make the holder let go sooner, or make the waiter wait longer or less often.
Fix 1: keep write transactions short
This is the real fix. Do slow work (network calls, parsing, user input) before you begin a transaction, then write and commit straight away. In Python's sqlite3, a transaction starts at your first INSERT, UPDATE or DELETE and lasts until commit(). A with conn: block commits for you.
rows = fetch_and_parse_everything() # slow work first, no transaction open
with conn: # then a short write
conn.executemany("INSERT INTO t VALUES (?)", rows)
Fix 2: set a busy timeout
A timeout tells SQLite how long to retry while another connection holds the lock, instead of failing at once. Python's default is 5 seconds. Raise it if you have occasional short collisions:
conn = sqlite3.connect("app.db", timeout=10)
conn.execute("PRAGMA busy_timeout = 10000") # the same idea, in milliseconds
Fix 3: use write-ahead logging (WAL)
In the default journal mode a writer blocks readers. In WAL mode readers and the writer proceed together. Turn it on once; it persists in the database file.
conn = sqlite3.connect("app.db")
print(conn.execute("PRAGMA journal_mode=WAL").fetchone()) # ('wal',)
WAL needs the database to be on a normal local disk. It is not reliable on some network file systems, and it adds two small side files (-wal and -shm) next to the database, so include them when you copy or back up the database while it is open.
Fix 4: stop holding a read open while you write
A pattern that surprises people: looping over a SELECT cursor and writing to the same database inside the loop. Fetch first, then write.
ids = [row[0] for row in conn.execute("SELECT id FROM jobs WHERE done = 0")] # read finished
with conn:
conn.executemany("UPDATE jobs SET done = 1 WHERE id = ?", [(i,) for i in ids])
If it keeps happening
If several processes write constantly (a web application with many workers, plus scheduled jobs), you are asking SQLite to do something it was designed not to do. Either route all writes through one process or queue, or move to a server database such as PostgreSQL, where concurrent writers are the normal case.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| sqlite3.OperationalError: database is locked | Another connection holds a write lock (an open transaction that has written but not committed) for longer than your busy timeout. | Find the code that leaves a transaction open and commit sooner. Raise the timeout as a safety net, and use WAL mode so readers stop blocking the writer. |
| The error appears only under load, or only when a second process (a web server worker, a scheduled job, a notebook) is open | SQLite allows many readers but one writer at a time, across all connections to the file. | Funnel writes through one place, or keep write transactions very short. If you need many concurrent writers, a server database is the right tool. |
| The error is raised immediately, with no waiting | The busy timeout is zero, or the connection was opened with a tiny value. | Open the connection with timeout=10 (Python) or run PRAGMA busy_timeout = 10000. |
| It happens when a read loop also writes | A SELECT cursor is still open while the same code writes, and an implicit transaction is held open. | Fetch the rows first (fetchall()) or finish the read before writing, and commit promptly. |
Frequently asked questions
Does WAL mode fix everything?
It lets readers and a writer work at the same time, which removes most of these errors. There is still only one writer at a time.
Is a longer timeout a good fix?
It hides the symptom. It is right as a safety net, but if a transaction is held open for seconds, shorten that transaction first.
References
From Grokem: https://www.grokem.com/databases/relational/sqlite-database-is-locked




