Concurrency (Biggest Gotcha)
Only one writer at a time—concurrent writes queue or fail; not for high-write workloads
Enable WAL mode:
PRAGMA journal_mode=WAL—allows reads during writes, huge improvementSet busy timeout:
PRAGMA busy_timeout=5000—waits 5s before SQLITE_BUSY instead of failing immediatelyWAL needs
-waland-shmfiles—don't forget to copy them with main databaseBEGIN IMMEDIATEto grab write lock early—prevents deadlocks in read-then-write patterns
Foreign Keys (Off by Default!)
PRAGMA foreign_keys=ONrequired per connection—not persisted in databaseWithout it, foreign key constraints silently ignored—data integrity broken
Check before relying:
PRAGMA foreign_keysreturns 0 or 1ON DELETE CASCADE only works if foreign_keys is ON
Type System
Type affinity, not strict types—INTEGER column accepts "hello" without error
STRICTtables enforce types—but only SQLite 3.37+ (2021)No native DATE/TIME—use TEXT as ISO8601 or INTEGER as Unix timestamp
BOOLEAN doesn't exist—use INTEGER 0/1; TRUE/FALSE are just aliases
REAL is 8-byte float—same precision issues as any float
Schema Changes
ALTER TABLEvery limited—can add column, rename table/column; that's mostly itCan't change column type, add constraints, or drop columns (until 3.35)
Workaround: create new table, copy data, drop old, rename—wrap in transaction
ALTER TABLE ADD COLUMNcan't have PRIMARY KEY, UNIQUE, or NOT NULL without default
Performance Pragmas
PRAGMA optimizebefore closing long-running connections—updates query planner statsPRAGMA cache_size=-64000for 64MB cache—negative = KB; default very smallPRAGMA synchronous=NORMALwith WAL—good balance of safety and speedPRAGMA temp_store=MEMORYfor temp tables in RAM—faster sorts and temp results
Vacuum & Maintenance
Deleted data doesn't shrink file—
VACUUMrewrites entire database, reclaims spaceVACUUMneeds 2x disk space temporarily—ensure enough roomPRAGMA auto_vacuum=INCREMENTALwithPRAGMA incremental_vacuum—partial reclaim without full rewriteAfter bulk deletes, always vacuum or file stays bloated
Backup Safety
Never copy database file while open—corrupts if write in progress
Use
.backupcommand in sqlite3—orsqlite3_backup_*APIWAL mode:
-waland-shmmust be copied atomically with main fileVACUUM INTO 'backup.db'creates standalone copy (3.27+)
Indexing
Covering indexes work—add extra columns to avoid table lookup
Partial indexes supported (3.8+):
CREATE INDEX ... WHERE conditionExpression indexes (3.9+):
CREATE INDEX ON t(lower(name))EXPLAIN QUERY PLANshows index usage—simpler than PostgreSQL EXPLAIN
Transactions
Autocommit by default—each statement is own transaction; slow for bulk inserts
Batch inserts:
BEGIN; INSERT...; INSERT...; COMMIT—10-100x fasterBEGIN EXCLUSIVEfor exclusive lock—blocks all other connectionsNested transactions via
SAVEPOINT name/RELEASE name/ROLLBACK TO name
Common Mistakes
Using SQLite for web app with concurrent users—one writer blocks all; use PostgreSQL
Assuming ROWID is stable—
VACUUMcan change ROWIDs; use explicit INTEGER PRIMARY KEYNot setting busy_timeout—random SQLITE_BUSY errors under any concurrency
In-memory database
':memory:'—each connection gets different database; usefile::memory:?cache=sharedfor shared