database is locked
SQLITE_BUSY (result code 5) is returned when a write operation cannot acquire the necessary database-level lock because another connection holds a conflicting lock. Unlike SQLITE_LOCKED which is object-level, SQLITE_BUSY is a database-file-level conflict. It is common in multi-process or multi-threaded environments where each process opens its own connection to the same database file.
- 1Another process has the database open with a write transaction in progress
- 2A long-running reader in WAL mode has created a snapshot that prevents a checkpoint
- 3The application opened a BEGIN IMMEDIATE or BEGIN EXCLUSIVE transaction on one connection while another connection holds a read transaction
- 4The busy timeout is set to 0 (default) and no retry logic is implemented
Two Python processes both open the same SQLite file and attempt concurrent writes without a busy timeout.
# Process 1
import sqlite3
conn1 = sqlite3.connect('shared.db')
conn1.execute('BEGIN EXCLUSIVE')
# Process 2 (simultaneously, in another terminal)
import sqlite3
conn2 = sqlite3.connect('shared.db')
conn2.execute('BEGIN EXCLUSIVE') # triggers SQLITE_BUSYexpected output
sqlite3.OperationalError: database is locked
Fix 1
Set a busy timeout
WHEN When transient lock contention is expected and automatic retry is acceptable.
import sqlite3
conn = sqlite3.connect('shared.db')
conn.execute('PRAGMA busy_timeout = 5000') # wait up to 5 seconds
# Or use the isolation_level parameter:
conn = sqlite3.connect('shared.db', timeout=5.0)Why this works
busy_timeout instructs the SQLite library to sleep and retry the lock acquisition for up to the specified number of milliseconds before returning SQLITE_BUSY. The Python sqlite3 module exposes this as the timeout parameter on connect().
Fix 2
Enable WAL mode for better read/write concurrency
WHEN When multiple readers and one writer need to operate simultaneously.
import sqlite3
conn = sqlite3.connect('shared.db')
conn.execute('PRAGMA journal_mode=WAL')
conn.commit()Why this works
WAL (Write-Ahead Logging) mode separates readers from writers: readers read the last committed snapshot in the WAL file while a writer appends to the WAL. This eliminates most SQLITE_BUSY errors caused by reader-writer conflicts because readers never block writers and writers never block readers.
✕ Retry in a tight loop without sleep
Hammering lock acquisition wastes CPU and increases contention. Use busy_timeout to let SQLite handle retries with exponential back-off internally.
WAL mode introduced. Strongly recommended for any multi-connection workload.
sqlite3.h — SQLITE_BUSY = 5
SQLite WAL mode documentation ↗SQLite locking and concurrency ↗Content generated with AI assistance and reviewed for accuracy. Found an error? hello@errcodes.dev