Skip to content

Grokem · https://www.grokem.com/databases/relational/sqlite-database-is-locked

RelationalTroubleshooting guide

SQLite: 'database is locked' and how to fix it

Why SQLite refuses a second writer, how to reproduce the error in six lines, and the changes (shorter transactions, a timeout, WAL mode) that stop it.

Grokem Editorial

Editorial desk

Published
  • 3 min read
Tested withPython3.11SQLite3.42

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.

python
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())
text
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.

python
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:

python
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.

python
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.

python
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

ProblemLikely causeFix
sqlite3.OperationalError: database is lockedAnother 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 openSQLite 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 waitingThe 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 writesA 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

How did this strike you?

Be the first to react – it is anonymous.

Comments

No comments yet. Share what worked for you, or ask a question.

Keep going

All in topic

Choose what we may use. You can change this at any time with “Privacy choices” at the foot of every page. Details are in the Cookie policy and the Privacy policy.

Strictly necessary

Remembers your theme, the lessons you marked as done and this choice. It stays on your device, tracks nothing and cannot be switched off.

Always on
What exactly is collected, and why

Grokem’s own counts

Collects
The pages you open and how long you stay, how far you scroll, where you came from, your device type, browser, screen size, language and time zone. Your IP address is turned into a code that changes every day; it is never stored as it is.
Why
To see how long lessons are read and where readers leave. Not shared with anyone.
Stored
A random visitor ID, a visit ID and the time of your last page view in your browser’s storage (gr_vid, gr_sid, gr_last). No name or email is attached.