Turning SQLite Into a Concurrency‑Friendly Engine: A Practical Guide to Write‑Ahead Logging
If your mobile app hits “database is locked” errors, Write‑Ahead Logging (WAL) may be the fix. This post explains what WAL is, how to enable it, the performance trade‑offs, and how to verify the change.
17 Sept 2026, 06:23 UTC

The Locking Problem in Mobile SQLite
Most of us have seen the dreaded database is locked error when a mobile app attempts to write while another thread is reading or writing. SQLite’s default rollback journal keeps the entire database file in a temporary state until a transaction commits. That means every write blocks readers, and every reader blocks writers. On a device with limited CPU and I/O, the result is sluggish data entry and occasional crashes.
What Is Write‑Ahead Logging?
Write‑Ahead Logging (WAL) changes the way SQLite records changes. Instead of writing directly to the main database file, it appends them to a separate .wal file. Readers can continue to read the main file while writers write to the log. When a transaction commits, the log is later merged into the database during a checkpoint. This separation allows a reader and a writer to run concurrently, dramatically reducing lock contention.
Enabling WAL in Your App
There are two common ways to switch to WAL mode:
- Runtime pragma – run
PRAGMA journal_mode=WAL;once per connection. The change persists for that database file until the mode is changed again. - Open flag – when creating a connection in C, pass
SQLITE_OPEN_WALtosqlite3_open_v2. In higher‑level bindings, look for an equivalent flag or option.
Below is a minimal example in Python that demonstrates enabling WAL and checking the mode:
import sqlite3
# Connect to the database (creates it if missing)
conn = sqlite3.connect('app.db', detect_types=sqlite3.PARSE_DECLTYPES)
cur = conn.cursor()
# Enable WAL mode – this returns the new journal mode
cur.execute('PRAGMA journal_mode=WAL;')
print('Journal mode:', cur.fetchone()[0]) # should print "wal"
# Do some work
cur.execute('CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, text TEXT);')
conn.commit()
# Clean up
conn.close()
After running the script, you should see app.db and app.db-wal in the same directory. The presence of the .wal file confirms that WAL is active.
Performance & Trade‑offs
- Throughput – In mixed read/write workloads, WAL can double throughput compared to rollback mode because readers no longer block writers.
- Disk Space – The
.walfile can grow to roughly 1.5× the size of the main database during heavy write periods. PeriodicVACUUMorPRAGMA wal_autocheckpointhelps keep it in check. - Checkpointing – When a checkpoint runs, the log is merged back into the main file. Checkpoints occur automatically after a certain number of log records, but you can trigger one manually with
PRAGMA wal_checkpoint;. - Filesystem support – WAL requires a filesystem that supports atomic rename and write operations. Network mounts (e.g., NFS) or older Android versions may not support it, causing SQLite to fall back to rollback mode.
- First‑write latency – The initial write to a fresh
.walfile can be slightly slower because the log file is created and the database is locked until the first checkpoint.
Checking That WAL Is Working
Run the following steps on a test device or emulator:
- Open a shell and navigate to the database directory.
- Execute
sqlite3 app.db "PRAGMA journal_mode;"– the output should bewal. - Run a small multi‑thread test (e.g., spawn two Python threads: one inserting rows, one selecting). If you see no
database is lockederrors, WAL is functioning. - After heavy writes, list the file sizes:
ls -lh app.db app.db-wal. The.walfile should be larger but not excessively so; if it’s >2× the database size, consider runningVACUUM.
Practical Checklist
- Confirm that your target platform’s filesystem supports WAL.
- Enable WAL once per database and keep the setting persistent.
- Schedule
VACUUMor setPRAGMA wal_autocheckpoint = 1000;to keep the log file from ballooning. - In Android, use
SQLiteDatabase.enableWriteAheadLogging()or set the flag inSQLiteOpenHelper. - Test under load: simulate concurrent access and verify that no lock errors occur.
- Monitor disk usage on production devices; if the
.walfile grows beyond a threshold, trigger a checkpoint.
Conclusion
Switching to Write‑Ahead Logging is a low‑effort change that can unlock significant concurrency and performance improvements in mobile and embedded SQLite workloads. By following the steps above, you’ll reduce lock contention, increase throughput, and still maintain data integrity. Just remember to monitor the .wal file size and keep an eye on filesystem compatibility. Once you’ve verified the mode and tuned the checkpoint settings, your app will handle concurrent reads and writes more gracefully than ever.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.