Serverless, self-contained relational database engine stored in a single file, ideal for embedded applications, local storage, prototyping, and datasets up to several hundred gigabytes.
Table of Contents#
- Overview
- Installation
- Database Basics
- Schema Management
- Data Manipulation
- Transactions and Savepoints
- WAL Mode
- Concurrency and Locking
- Pragma Directives
- Indexing and Query Optimization
- Multi-Database with ATTACH
- Backup Strategies
- Performance Tuning
- Troubleshooting
- See Also
- Sources
1. Overview#
SQLite is a C library that implements a complete, ACID-compliant SQL database engine in a single file. Unlike client-server databases, SQLite reads and writes directly to ordinary disk files. A complete database (tables, indexes, triggers, views) is contained in one cross-platform file.
Key characteristics:
- Zero configuration - no server process, no setup, no administration
- Self-contained - single library with no external dependencies
- ACID compliant - atomic, consistent, isolated, durable transactions
- Cross-platform - database files are portable between 32-bit and 64-bit, big-endian and little-endian systems
- Size limits - maximum database size of 281 TB; practical limit depends on filesystem
- Public domain - no license restrictions
When to Use SQLite#
| Good Fit | Poor Fit |
|---|---|
| Embedded/mobile applications | High-concurrency write workloads |
| Desktop applications | Multi-server web applications |
| Testing and prototyping | Datasets requiring fine-grained access control |
| Data analysis and ETL | Write-heavy workloads with many concurrent writers |
| Configuration/cache storage | Applications needing client-server architecture |
| Single-user web applications | Replication or clustering requirements |
2. Installation#
2.1 Linux#
# Debian/Ubuntu
sudo apt install -y sqlite3
# RHEL/Rocky/Alma
sudo dnf install -y sqlite
# Arch Linux
sudo pacman -S sqlite2.2 Verify#
sqlite3 --version3. Database Basics#
3.1 Creating and Opening Databases#
# Create or open a database (creates file if it doesn't exist)
sqlite3 <database_name>.db
# Open in read-only mode
sqlite3 -readonly <database_name>.db
# Execute a command and exit
sqlite3 <database_name>.db "SELECT * FROM users;"3.2 Shell Commands (Dot Commands)#
| Command | Description |
|---|---|
.help | Show all dot commands |
.databases | List attached databases |
.tables | List tables |
.schema <table> | Show CREATE statement |
.headers on | Show column headers in output |
.mode column | Columnar output format |
.mode csv | CSV output format |
.mode json | JSON output format |
.output <file> | Redirect output to file |
.import <file> <table> | Import CSV into table |
.dump | Export entire database as SQL |
.dump <table> | Export single table as SQL |
.read <file> | Execute SQL from file |
.quit | Exit the shell |
.timer on | Show query execution time |
.eqp on | Show query plan for every query |
3.3 Deleting a Database#
SQLite databases are ordinary files. Delete them with your filesystem tools:
rm <database_name>.db
# Also remove WAL and SHM files if they exist
rm -f <database_name>.db-wal <database_name>.db-shm4. Schema Management#
4.1 Creating Tables#
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
created_at TEXT DEFAULT (datetime('now')),
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'banned'))
);
-- Without AUTOINCREMENT (reuses deleted rowids, faster)
CREATE TABLE logs (
id INTEGER PRIMARY KEY,
message TEXT NOT NULL,
level TEXT NOT NULL,
timestamp TEXT DEFAULT (datetime('now'))
);4.2 Modifying Tables#
SQLite has limited ALTER TABLE support:
-- Add a column
ALTER TABLE users ADD COLUMN phone TEXT;
-- Rename a column (SQLite 3.25+)
ALTER TABLE users RENAME COLUMN phone TO phone_number;
-- Rename a table
ALTER TABLE users RENAME TO app_users;
-- Drop a column (SQLite 3.35+)
ALTER TABLE users DROP COLUMN phone_number;For operations not supported by ALTER TABLE (changing column types, adding constraints), use the rebuild pattern:
BEGIN TRANSACTION;
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
age INTEGER CHECK (age > 0)
);
INSERT INTO users_new (id, name, email) SELECT id, name, email FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;
COMMIT;4.3 Deleting Tables#
DROP TABLE IF EXISTS users;4.4 Views#
CREATE VIEW active_users AS
SELECT id, name, email FROM users WHERE status = 'active';
DROP VIEW IF EXISTS active_users;5. Data Manipulation#
5.1 Inserting Data#
-- Single row
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
-- Multiple rows
INSERT INTO users (name, email) VALUES
('Bob', 'bob@example.com'),
('Carol', 'carol@example.com'),
('Dave', 'dave@example.com');
-- Insert or replace on conflict
INSERT OR REPLACE INTO users (id, name, email)
VALUES (1, 'Alice Updated', 'alice@example.com');
-- Upsert (SQLite 3.24+)
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
ON CONFLICT(email) DO UPDATE SET name = excluded.name;5.2 Querying Data#
-- Basic select
SELECT * FROM users;
-- With conditions and ordering
SELECT name, email FROM users
WHERE status = 'active'
ORDER BY name ASC
LIMIT 10 OFFSET 20;
-- Aggregation
SELECT status, COUNT(*) as count
FROM users
GROUP BY status
HAVING count > 5;
-- Window functions (SQLite 3.25+)
SELECT name, email,
ROW_NUMBER() OVER (ORDER BY created_at) as row_num
FROM users;
-- JSON functions (SQLite 3.38+)
SELECT json_extract(metadata, '$.role') as role
FROM users
WHERE json_extract(metadata, '$.active') = 1;
-- Common Table Expressions
WITH recent AS (
SELECT * FROM users WHERE created_at > datetime('now', '-7 days')
)
SELECT * FROM recent WHERE status = 'active';5.3 Updating Data#
UPDATE users SET status = 'inactive'
WHERE created_at < datetime('now', '-1 year');5.4 Deleting Data#
DELETE FROM users WHERE status = 'banned';
-- Delete all rows (faster than DELETE without WHERE)
DELETE FROM users;6. Transactions and Savepoints#
6.1 Basic Transactions#
BEGIN TRANSACTION;
INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
INSERT INTO logs (message, level) VALUES ('User created: Eve', 'INFO');
COMMIT;
-- Or rollback on error
BEGIN TRANSACTION;
INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
-- Something went wrong
ROLLBACK;6.2 Transaction Types#
-- DEFERRED (default): acquires lock only when needed
BEGIN DEFERRED TRANSACTION;
-- IMMEDIATE: acquires RESERVED lock immediately (prevents other writers)
BEGIN IMMEDIATE TRANSACTION;
-- EXCLUSIVE: acquires EXCLUSIVE lock immediately (prevents all other access)
BEGIN EXCLUSIVE TRANSACTION;For write transactions, prefer BEGIN IMMEDIATE to avoid SQLITE_BUSY errors from lock escalation:
BEGIN IMMEDIATE;
UPDATE users SET status = 'inactive' WHERE id = 42;
COMMIT;6.3 Savepoints#
Savepoints create nested transaction points within a transaction:
BEGIN TRANSACTION;
INSERT INTO users (name, email) VALUES ('Frank', 'frank@example.com');
SAVEPOINT before_logs;
INSERT INTO logs (message, level) VALUES ('Test entry', 'DEBUG');
-- Undo just the log entry
ROLLBACK TO SAVEPOINT before_logs;
-- Frank's insert is still staged
COMMIT;7. WAL Mode#
Write-Ahead Logging (WAL) mode significantly improves concurrency by allowing readers and a single writer to operate simultaneously without blocking each other.
7.1 Enable WAL Mode#
PRAGMA journal_mode=WAL;This is persistent; the database stays in WAL mode until explicitly changed back.
7.2 WAL vs Rollback Journal#
| Feature | Rollback Journal (default) | WAL Mode |
|---|---|---|
| Concurrent readers during write | No (blocked) | Yes |
| Write performance | Good | Better (sequential writes) |
| Read performance during writes | Blocked | Unblocked |
| File count | 1 (.db) | 3 (.db, .db-wal, .db-shm) |
| Network filesystem support | Yes | No (requires shared memory) |
| Crash recovery | Slower | Faster |
7.3 WAL Checkpointing#
The WAL file must be periodically checkpointed (flushed to the main database):
-- Automatic checkpointing threshold (default 1000 pages)
PRAGMA wal_autocheckpoint=1000;
-- Manual checkpoint
PRAGMA wal_checkpoint(PASSIVE); -- Non-blocking, checkpoint what's possible
PRAGMA wal_checkpoint(FULL); -- Block until all frames checkpointed
PRAGMA wal_checkpoint(TRUNCATE); -- Full + truncate WAL file to zero bytes7.4 WAL Mode Limitations#
- Does not work on network filesystems (NFS, SMB) because it requires shared memory (
mmap) - Creates two additional files (
.db-waland.db-shm) alongside the database - Long-running read transactions can prevent WAL checkpointing, causing the WAL file to grow
8. Concurrency and Locking#
8.1 Lock States#
SQLite uses a file-level locking system with five states:
| Lock State | Description |
|---|---|
| UNLOCKED | No lock held; default state |
| SHARED | Reading; multiple readers allowed simultaneously |
| RESERVED | Preparing to write; only one RESERVED lock at a time; readers still allowed |
| PENDING | Waiting for SHARED locks to clear before writing |
| EXCLUSIVE | Writing; no other connections can read or write |
8.2 Busy Timeout#
When a connection encounters a locked database, it can wait instead of failing immediately:
-- Wait up to 5 seconds for locks to clear
PRAGMA busy_timeout=5000;In application code, always set a busy timeout:
sqlite3 mydb.db "PRAGMA busy_timeout=5000;"8.3 Concurrency Best Practices#
- Enable WAL mode for all multi-connection use cases
- Use
BEGIN IMMEDIATEfor write transactions to fail fast on contention - Keep transactions short; commit as soon as possible
- Set
busy_timeoutto a reasonable value (1000-30000 ms) - For highest write throughput, funnel all writes through a single connection
- Avoid
BEGIN EXCLUSIVEunless absolutely necessary
9. Pragma Directives#
Pragmas are SQLite-specific commands that control database behavior:
9.1 Essential Pragmas#
-- Performance
PRAGMA journal_mode=WAL; -- Enable WAL mode
PRAGMA synchronous=NORMAL; -- NORMAL is safe with WAL; FULL is safest
PRAGMA cache_size=-64000; -- 64 MB page cache (negative = KB)
PRAGMA mmap_size=268435456; -- 256 MB memory-mapped I/O
PRAGMA temp_store=MEMORY; -- Store temporary tables in memory
-- Integrity
PRAGMA foreign_keys=ON; -- Enforce foreign key constraints (off by default)
PRAGMA integrity_check; -- Full database integrity check
PRAGMA quick_check; -- Faster, less thorough check
-- Information
PRAGMA table_info(<table>); -- Column information
PRAGMA index_list(<table>); -- Indexes on a table
PRAGMA compile_options; -- SQLite compile-time options
PRAGMA database_list; -- Attached databases9.2 Recommended Production Pragmas#
Apply these at connection open (they do not persist across connections except journal_mode):
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA busy_timeout=5000;
PRAGMA cache_size=-64000;
PRAGMA foreign_keys=ON;
PRAGMA temp_store=MEMORY;
PRAGMA mmap_size=268435456;9.3 Synchronous Modes#
| Mode | Safety | Performance | Notes |
|---|---|---|---|
OFF | Risk of corruption on crash | Fastest | Never use in production |
NORMAL | Safe with WAL mode | Fast | Recommended with WAL |
FULL | Safe in all modes | Slower | Default; safest choice |
EXTRA | Maximum safety | Slowest | Rarely needed |
10. Indexing and Query Optimization#
10.1 Creating Indexes#
-- Single column index
CREATE INDEX idx_users_email ON users(email);
-- Compound index
CREATE INDEX idx_logs_level_time ON logs(level, timestamp);
-- Unique index
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);
-- Partial index (SQLite 3.8.0+)
CREATE INDEX idx_active_users ON users(name) WHERE status = 'active';
-- Expression index (SQLite 3.9.0+)
CREATE INDEX idx_users_lower_name ON users(lower(name));10.2 Query Plan Analysis#
-- Show the query plan
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'alice@example.com';
-- Full bytecode explanation
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';
-- Enable automatic query plan display
.eqp onLook for SCAN (full table scan, usually bad) vs SEARCH (index lookup, usually good).
10.3 Analyzing Statistics#
-- Update query planner statistics
ANALYZE;
-- Analyze a specific table
ANALYZE users;
-- View statistics
SELECT * FROM sqlite_stat1;10.4 Index Best Practices#
- Index columns used in WHERE, JOIN, and ORDER BY clauses
- Compound indexes should list equality columns first, then range columns
- Avoid over-indexing; each index costs write performance and storage
- Use partial indexes for queries that always filter on the same condition
- Run
ANALYZEafter significant data changes
11. Multi-Database with ATTACH#
SQLite can attach multiple database files to a single connection, enabling cross-database queries:
-- Attach another database
ATTACH DATABASE '/path/to/other.db' AS other;
-- Query across databases
SELECT u.name, o.order_date
FROM main.users u
JOIN other.orders o ON u.id = o.user_id;
-- List attached databases
PRAGMA database_list;
-- Detach
DETACH DATABASE other;Limitations#
- Transactions span all attached databases (atomic across all)
- Maximum 10 attached databases by default (compile-time configurable)
- ATTACH is not allowed inside a transaction in WAL mode
12. Backup Strategies#
12.1 SQL Dump#
# Full dump (portable, human-readable)
sqlite3 <database>.db .dump > backup.sql
# Restore from dump
sqlite3 <database_new>.db < backup.sql12.2 Online Backup API#
The .backup command uses SQLite's online backup API, which is safe even while the database is in use:
# Backup to file
sqlite3 <database>.db ".backup /backup/db-$(date +%Y%m%d).db"12.3 File Copy (WAL Mode Considerations)#
In WAL mode, you must checkpoint and copy all three files:
# Checkpoint first to flush WAL to main database
sqlite3 <database>.db "PRAGMA wal_checkpoint(TRUNCATE);"
# Then copy the database file
cp <database>.db /backup/<database>-$(date +%Y%m%d).dbIf you cannot checkpoint (database is busy), copy all three files atomically:
cp <database>.db <database>.db-wal <database>.db-shm /backup/12.4 Automated Backup Script#
#!/bin/bash
# /usr/local/bin/sqlite-backup.sh
DB_PATH="<database>.db"
BACKUP_DIR="/backup/sqlite"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "${BACKUP_DIR}"
sqlite3 "${DB_PATH}" ".backup ${BACKUP_DIR}/backup-${DATE}.db"
if [ $? -eq 0 ]; then
echo "Backup successful"
find "${BACKUP_DIR}" -name "*.db" -mtime +${RETENTION_DAYS} -delete
else
echo "Backup FAILED" >&2
exit 1
fi12.5 VACUUM INTO (SQLite 3.27+)#
Creates a compacted copy of the database:
VACUUM INTO '/backup/compacted.db';This is safe during concurrent access and produces a defragmented copy.
13. Performance Tuning#
13.1 Bulk Insert Optimization#
-- Wrap bulk inserts in a transaction (50x+ faster)
PRAGMA synchronous=OFF; -- Only during initial load
BEGIN TRANSACTION;
INSERT INTO data VALUES (1, 'a');
INSERT INTO data VALUES (2, 'b');
-- ... thousands more
COMMIT;
PRAGMA synchronous=NORMAL;13.2 Memory-Mapped I/O#
-- Map 256 MB of database into memory
PRAGMA mmap_size=268435456;This allows the OS to handle caching via the page cache, which can dramatically speed up read-heavy workloads.
13.3 VACUUM#
Over time, deleted and updated rows leave free space in the database file. VACUUM reclaims this space:
-- Full vacuum (rebuilds entire database, requires 2x disk space temporarily)
VACUUM;
-- Auto-vacuum (reclaims space automatically, slight overhead)
PRAGMA auto_vacuum=INCREMENTAL;
PRAGMA incremental_vacuum(100); -- Free up to 100 pages13.4 Size and Speed Comparison#
| Configuration | Relative Write Speed | Durability |
|---|---|---|
| Default (rollback journal, FULL sync) | 1x (baseline) | Maximum |
| WAL mode, NORMAL sync | ~5x | High |
| WAL mode, NORMAL sync, large cache | ~10x | High |
| In-transaction batch, NORMAL sync | ~50x | High |
| OFF sync (dangerous) | ~100x | Data loss risk on crash |
14. Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
database is locked | Another connection holds an exclusive lock | Set PRAGMA busy_timeout=5000; use WAL mode; keep transactions short |
SQLITE_BUSY on write | Concurrent writers in rollback journal mode | Switch to WAL mode; use BEGIN IMMEDIATE |
| Database file keeps growing | Deleted rows leave free pages | Run VACUUM; or enable auto_vacuum |
| Slow queries on large tables | Missing indexes or stale statistics | Run EXPLAIN QUERY PLAN; add indexes; run ANALYZE |
database disk image is malformed | File corruption (crash, bad disk, NFS) | Run PRAGMA integrity_check; restore from backup |
| WAL file growing unbounded | Long-running read transaction prevents checkpoint | Close long-lived read connections; run PRAGMA wal_checkpoint(TRUNCATE) |
| Foreign keys not enforced | foreign_keys pragma defaults to OFF | Set PRAGMA foreign_keys=ON at every connection open |
| Poor write performance | Autocommit mode (each INSERT is its own transaction) | Batch writes in explicit transactions |
| Cannot use WAL on NFS | WAL requires shared memory (mmap) | Use rollback journal mode on network filesystems |