Solving SQLite Read-Write Contention with WAL Mode
Learn how to eliminate 'Database is Locked' errors in SQLite by implementing Write-Ahead Logging (WAL) to allow concurrent reads and writes.
24 Sept 2026, 19:09 UTC

The 'Database is Locked' Bottleneck
In a standard SQLite configuration, the database operates using a rollback journal. This means that whenever a process needs to write to the database, it acquires an exclusive lock. While that write is happening, every other connection—even those just trying to read a single row—is blocked. In a multi-threaded application or a web server, this manifests as the dreaded SQLITE_BUSY error.
The solution for most concurrency issues in SQLite is Write-Ahead Logging (WAL). Instead of locking the main database file for every update, WAL allows readers to continue reading the original database file while writes are appended to a separate log file. This effectively decouples reads from writes.
How WAL Changes the Locking Model
In default rollback mode, SQLite writes changes directly to the database file and keeps the original data in a journal to revert if something fails. This requires a "stop-the-world" lock on the file.
When you enable WAL mode, SQLite creates two additional files alongside your main .db file: a -wal file and a -shm (shared memory) file. The -wal file stores the new transactions. When a reader requests data, SQLite checks both the main database and the WAL file to provide the most recent version of the data. Because the main database file remains untouched during the write process, readers never have to wait for a writer to finish.
Implementing WAL Mode
Enabling WAL is a persistent setting. You only need to run the command once per database file, and SQLite will remember the mode across restarts.
Configuration Example
Run the following command from your database management tool or within your application code using a connection with write permissions:
-- Run this on the database connection
PRAGMA journal_mode=WAL;
Verification: To confirm the change was successful, execute the same command again. SQLite will return a result string. If it returns wal, the mode is active.
Diagnostic Check
To verify the behavior in a live environment, you can perform these checks:
- File System: Look at the directory containing your database. You should see
yourdb.db-walandyourdb.db-shmfiles appear as soon as the first write occurs. - Concurrency Test: Open two separate terminal sessions or application threads. Start a long-running transaction in one (e.g., a large
INSERTloop) and attempt aSELECTquery in the other. In WAL mode, theSELECTwill return immediately without waiting for theINSERTto commit.
The Checkpoint Trade-off
WAL mode is not a "set and forget" miracle; it introduces a new requirement called checkpointing. Since changes are stored in the -wal file, that file will grow indefinitely unless those changes are moved back into the main .db file.
SQLite handles this automatically by default (usually when the WAL file reaches 1,000 pages), but this can lead to unpredictable latency spikes. If a massive checkpoint triggers during a critical operation, you may see a temporary dip in performance as the disk performs heavy I/O to merge the files.
Critical Limitations
| Constraint | Impact |
|---|---|
| Network Filesystems | WAL does not work on NFS or SMB shares because it relies on shared memory (-shm) which is not supported across networks. |
| Disk Space | The -wal file can grow quite large if a read transaction stays open for a long time, preventing the checkpoint from completing. |
Summary for Implementation
If your application is experiencing SQLITE_BUSY errors during concurrent access and your database is stored on a local disk, switching to WAL mode is the most effective optimization. Simply execute PRAGMA journal_mode=WAL;, verify the presence of the -wal file, and monitor your disk space to ensure checkpoints are occurring regularly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.