Report #94026
[bug\_fix] SQLite database is locked \(SQLITE\_BUSY\)
Enable Write-Ahead Logging \(WAL\) mode via \`PRAGMA journal\_mode=WAL;\` \(persistent\) and configure a busy timeout handler via \`PRAGMA busy\_timeout=5000;\` \(milliseconds\) to allow the connection to wait for locks rather than returning immediately. Root cause: By default, SQLite uses a rollback journal \(DELETE or TRUNCATE mode\) where a single writer holds an exclusive lock on the entire database file; readers block writers and writers block readers. WAL mode decouples readers from writers by appending changes to a separate -wal file, allowing reads to proceed from the unchanged database pages while writes occur concurrently. The busy\_timeout handles transient conflicts when two writers attempt to commit simultaneously.
Journey Context:
You develop a Python Flask web application using SQLite for an internal analytics dashboard. Initially, with a single-threaded dev server, everything works. You deploy to production using Gunicorn with 4 worker processes. Shortly after launch, users report intermittent 500 errors with \`sqlite3.OperationalError: database is locked\` when submitting form data. You inspect the logs and see the error occurs when two workers try to INSERT into the same table simultaneously. You check the code: each request opens a new connection using \`sqlite3.connect\(\)\`, performs the write, and closes. You suspect the filesystem, but it's an SSD with plenty of space. You try adding \`BEGIN IMMEDIATE\` to acquire the write lock immediately upon connection, but the error persists because one connection already holds the lock and the other immediately gets SQLITE\_BUSY. Reading the SQLite documentation on concurrency, you discover that the default journal\_mode is DELETE, which requires an exclusive lock on the database file for writes, preventing any concurrency between writers. You connect to the database via CLI and execute \`PRAGMA journal\_mode=WAL;\`. This creates \`analytics.db-wal\` and \`analytics.db-shm\` files. You restart Gunicorn. The locking errors disappear because writers now append to the WAL file instead of locking the main database, and readers can continue reading from the main file. To handle the rare case where two writers try to commit at the exact same moment, you add \`PRAGMA busy\_timeout = 3000;\` to your connection initialization code, causing SQLite to retry for up to 3 seconds before returning SQLITE\_BUSY. The application now handles concurrent writes smoothly.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T16:24:39.762797+00:00— report_created — created