SQLite’s database is locked error means another connection was using the database file when yours needed it, and yours gave up waiting. Nothing is broken, and the fix depends on what’s holding the lock. In this article I’ll walk you through finding it and the fixes that make the error go away.
TLDR
- The error means another connection holds the lock your connection needs, and yours gave up waiting.
- Set a busy timeout so connections wait instead of failing:
PRAGMA busy_timeout = 5000;, ortimeout=in Python. - Turn on WAL mode with
PRAGMA journal_mode = WAL;so readers stop blocking writers. - Start transactions that read and then write with
BEGIN IMMEDIATE. - Keep transactions short, and commit or close connections you no longer need.
What does “database is locked” mean in SQLite?
“database is locked” is the message for SQLite’s SQLITE_BUSY result code. The docs say it means “the database file could not be written (or in some cases read) because of concurrent activity by some other database connection” (result codes).
The root cause is SQLite’s locking model. It “only supports one writer at a time per database file” (Appropriate Uses For SQLite). So when one connection is writing, every other connection that wants to write has to wait. If it isn’t allowed to wait, or waits too long, it gets the error.
To show you the error on purpose, I wrote a script where connection a starts a write and doesn’t commit. Connection b is set not to wait at all, and tries to write:
import sqlite3
a = sqlite3.connect("app.db", isolation_level=None)
a.execute("CREATE TABLE IF NOT EXISTS log (msg TEXT)")
a.execute("BEGIN")
a.execute("INSERT INTO log VALUES ('from a')") # a now holds the write lock
b = sqlite3.connect("app.db", timeout=0, isolation_level=None) # don't wait at all
try:
b.execute("INSERT INTO log VALUES ('from b')")
except sqlite3.OperationalError as e:
print("b:", e)
a.execute("COMMIT")
$ python3 1_reproduce.py
b: database is locked
The settings that fix it
Every fix on this page comes down to one of three statements. They work from any SQLite client:
| Setting | Syntax | What it changes |
|---|---|---|
| Busy timeout | PRAGMA busy_timeout = milliseconds; | How long a connection retries before it fails |
| WAL mode | PRAGMA journal_mode = WAL; | Lets readers and one writer work at the same time |
| Immediate transaction | BEGIN IMMEDIATE; | Takes the write lock at the start of the transaction |
In Python, sqlite3.connect() has a timeout parameter for the busy timeout, which defaults to 5 seconds (sqlite3.connect()). In the sqlite3 shell, .timeout MS sets it.
How to fix “database is locked”
Work through these steps in order. The first one tells you who holds the lock, and the next three stop the error from coming back.
Step 1: Find what is holding the lock
A lock is held by an open transaction in some process. To show this, I kept a write transaction open for 3 seconds in a background Python script (hold_lock.py). Then I tried to write from the shell, and asked lsof which process had the file open:
import sqlite3, time
con = sqlite3.connect("app.db", isolation_level=None)
con.execute("CREATE TABLE IF NOT EXISTS log (msg TEXT)")
con.execute("BEGIN IMMEDIATE") # take the write lock and keep it
con.execute("INSERT INTO log VALUES ('held')")
time.sleep(3)
con.execute("COMMIT")
$ sqlite3 app.db "INSERT INTO log VALUES ('from the shell');"
Error: stepping, database is locked (5)
$ lsof app.db | awk '{print $1, $2, $NF}'
COMMAND PID NAME
Python 89488 app.db
$ sqlite3 app.db ".timeout 5000" "INSERT INTO log VALUES ('after waiting');" "SELECT msg FROM log;"
held
after waiting
The shell failed at once, and lsof named the Python process. The same insert with .timeout 5000 waited for the lock and succeeded. There’s no “unlock” command in SQLite. To unlock the database, end the transaction that holds the lock: commit it, or close or stop that process. If the file is also open in a GUI tool such as DB Browser for SQLite, check that too: any program that keeps a write transaction open holds the lock in the same way.
Step 2: Wait with a busy timeout
A busy timeout makes a connection retry for a while before it gives up. Here a commits after half a second, and b waits for it:
import sqlite3, threading, time
a = sqlite3.connect("app.db", isolation_level=None, check_same_thread=False)
a.execute("CREATE TABLE IF NOT EXISTS log (msg TEXT)")
a.execute("BEGIN")
a.execute("INSERT INTO log VALUES ('from a')")
threading.Timer(0.5, lambda: a.execute("COMMIT")).start() # a commits after half a second
b = sqlite3.connect("app.db", timeout=5, isolation_level=None) # wait up to 5 seconds
start = time.monotonic()
b.execute("INSERT INTO log VALUES ('from b')")
print(f"b waited {time.monotonic() - start:.1f}s and then succeeded")
$ python3 2_busy_timeout.py
b waited 0.5s and then succeeded
A timeout covers short waits. It can’t help if the other connection holds its lock for longer than the timeout.
Step 3: Switch to WAL mode so readers don’t block writers
In the default rollback-journal mode, a writer can’t commit while another connection has a read transaction open. In WAL mode, “readers do not block writers and a writer does not block readers” (Write-Ahead Logging). I ran the same script in both journal modes:
import sqlite3, sys
mode = sys.argv[1] # "delete" (the default rollback journal) or "wal"
db = f"{mode}.db"
setup = sqlite3.connect(db, isolation_level=None)
setup.execute(f"PRAGMA journal_mode = {mode}")
setup.execute("CREATE TABLE IF NOT EXISTS log (msg TEXT)")
setup.execute("INSERT INTO log VALUES ('first')")
setup.close()
reader = sqlite3.connect(db, isolation_level=None)
reader.execute("BEGIN")
print("reader sees", reader.execute("SELECT count(*) FROM log").fetchone()[0], "row(s), transaction still open")
writer = sqlite3.connect(db, timeout=0.5, isolation_level=None)
writer.execute("BEGIN")
writer.execute("INSERT INTO log VALUES ('second')")
try:
writer.execute("COMMIT")
print(mode, "mode: writer committed while the reader was open")
except sqlite3.OperationalError as e:
print(mode, "mode: writer COMMIT failed:", e)
$ python3 3_reader_blocks_commit.py delete
reader sees 1 row(s), transaction still open
delete mode: writer COMMIT failed: database is locked
$ python3 3_reader_blocks_commit.py wal
reader sees 1 row(s), transaction still open
wal mode: writer committed while the reader was open
WAL is stored in the database file, so you set it once. More in SQLite WAL mode.
Step 4: Use BEGIN IMMEDIATE for transactions that read, then write
This case fails even with a long timeout. A plain BEGIN is deferred: “If the first statement after BEGIN DEFERRED is a SELECT, then a read transaction is started. Subsequent write statements will upgrade the transaction to a write transaction if possible, or return SQLITE_BUSY” (SQLite transactions). In this script, two threads each read a counter and then write it back:
import sqlite3, sys, threading, time
begin = sys.argv[1] # "BEGIN" (deferred) or "BEGIN IMMEDIATE"
db = "upgrade.db"
s = sqlite3.connect(db, isolation_level=None)
s.execute("PRAGMA journal_mode = wal")
s.execute("CREATE TABLE IF NOT EXISTS counter (n INTEGER)")
s.execute("DELETE FROM counter"); s.execute("INSERT INTO counter VALUES (0)")
s.close()
def increment(name, pause=0.0):
con = sqlite3.connect(db, timeout=5, isolation_level=None)
start = time.monotonic()
try:
con.execute(begin)
n = con.execute("SELECT n FROM counter").fetchone()[0] # read first...
time.sleep(pause) # ...do some work...
con.execute("UPDATE counter SET n = ?", (n + 1,)) # ...then write
con.execute("COMMIT")
print(f"{name}: committed after {time.monotonic() - start:.1f}s")
except sqlite3.OperationalError as e:
print(f"{name}: failed after {time.monotonic() - start:.1f}s: {e}")
con.close()
a = threading.Thread(target=increment, args=("a", 0.5))
b = threading.Thread(target=increment, args=("b", 0.0))
a.start(); time.sleep(0.1); b.start()
a.join(); b.join()
print("final value:", sqlite3.connect(db).execute("SELECT n FROM counter").fetchone()[0], "(two increments should give 2)")
$ python3 4_deferred_upgrade.py BEGIN
b: committed after 0.0s
a: failed after 0.5s: database is locked
final value: 1 (two increments should give 2)
$ python3 4_deferred_upgrade.py "BEGIN IMMEDIATE"
a: committed after 0.5s
b: committed after 0.5s
final value: 2 (two increments should give 2)
With BEGIN, a fails as soon as it tries to write, after its own 0.5 seconds of work and well before its 5-second timeout. b changed the data after a read it, so SQLite refuses the stale write instead of overwriting b‘s change. a would have to retry the whole transaction, and this script doesn’t, which is why the final value is 1. With BEGIN IMMEDIATE, each connection takes the write lock up front. b waits its turn, and both increments land. This example runs in WAL mode.
Other causes
If the error keeps coming back after those steps, check these:
- A transaction left open. A forgotten
COMMIT, or a connection that a driver keeps inside a transaction, holds its lock until the transaction ends. Commit promptly, and close connections you no longer need. - A network filesystem. SQLite’s docs warn that file locking “is known to be buggy or even unimplemented on many NFS implementations” and advise: “Your best defense is to not use SQLite for files on a network filesystem” (File Locking And Concurrency). WAL “does not work over a network filesystem” at all (Write-Ahead Logging).
- Many writers that can’t take turns. If writes really have to happen at the same instant, SQLite’s guidance is to use a client/server database (Appropriate Uses For SQLite).
Conclusion
“database is locked” means a second connection wanted the write lock and gave up waiting. Find the transaction that holds it first. After that, a busy timeout and WAL mode stop routine collisions, and BEGIN IMMEDIATE fixes transactions that read and then write.
Further reading
- Database is locked in Python
- SQLite WAL mode
- SQLite in Node.js
- SQLite PRAGMA
- All SQLite guides by topic
- OperationalError: unable to open database file, a different SQLite error
- SQLite docs: transactions
- SQLite docs: result codes
Tested with Python 3.13.14 (SQLite 3.53.4), plus the sqlite3 shell 3.51.0 and Python 3.9.6 for the lock-holder example, on macOS, 7 October 2026.
