Skip to main content
← All Articles · · 17 min read

SQLite vs PostgreSQL vs MySQL: Database Selection Guide 2026

A comprehensive technical comparison of SQLite, PostgreSQL, and MySQL for 2026 — covering ACID compliance, performance benchmarks, JSON support, replication, and which database to choose for your use case.

sqlitepostgresqlmysqldatabasesqlbackendcomparisonrelational-databaseperformance

The wrong database choice can limit your architecture for years. SQLite, PostgreSQL, and MySQL are the three most deployed relational databases in the world — each with distinct strengths, weaknesses, and sweet spots. In 2026, all three are mature, actively maintained, and production-ready. The question is which one fits your workload.


Quick Comparison: At a Glance

SQLitePostgreSQLMySQL
ArchitectureServerless, embeddedClient-serverClient-server
StorageSingle fileMultiple filesMultiple files
ACIDFull (WAL mode)FullFull (InnoDB)
ConcurrencyLimited (write lock)Excellent (MVCC)Good (MVCC)
JSON SupportBasicAdvanced (jsonb)Good
Full-Text SearchBasic FTS5Advanced (tsvector)Basic
ReplicationFile copy / LitestreamStreaming, logicalGroup replication
Max DB Size281 TB (theoretical)UnlimitedUnlimited
LicensePublic DomainPostgreSQL LicenseGPL / Commercial
Best ForEmbedded, testing, edgeComplex queries, analyticsWeb apps, read-heavy

SQLite: The Underestimated Powerhouse

SQLite is the most-deployed database in the world by a wide margin — it’s in every Android device, iOS app, browser, and most desktop software. It’s often dismissed as “just a testing database,” but that’s a misconception that’s changing fast in 2026.

Architecture: Serverless by Design

SQLite is a C library that reads and writes directly to a disk file. There is no server process, no network protocol, and no authentication system. The database is a single .db file.

import sqlite3

# No connection string — just a file path
conn = sqlite3.connect('app.db')
cursor = conn.cursor()

# WAL mode for better concurrent reads
cursor.execute('PRAGMA journal_mode=WAL')
cursor.execute('PRAGMA synchronous=NORMAL')
cursor.execute('PRAGMA cache_size=-64000')  # 64MB cache
cursor.execute('PRAGMA foreign_keys=ON')

# Create table
cursor.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        email TEXT UNIQUE NOT NULL,
        name TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
''')
conn.commit()

When SQLite Is Production-Ready

The “SQLite is just for development” myth dies here. In 2026, SQLite is a legitimate production choice for:

Single-server applications under 100k req/day. SQLite’s write throughput in WAL mode reaches ~50k writes/sec for simple inserts on NVMe storage. This exceeds many applications’ needs.

Edge computing and CDN workers. Cloudflare D1, Turso, and Fly.io’s SQLite offering run SQLite at the edge. The library embeds directly in the worker process — no network round-trip to a database server.

Embedded devices and desktop apps. The zero-administration model means no database server to manage, update, or secure.

// Turso (LibSQL / distributed SQLite) — 2026
import { createClient } from '@libsql/client';

const client = createClient({
  url: process.env.TURSO_DB_URL,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const result = await client.execute({
  sql: 'SELECT * FROM users WHERE id = ?',
  args: [userId],
});

SQLite Limitations

No concurrent writes. SQLite uses database-level write locking. Multiple processes can read simultaneously in WAL mode, but only one writer at a time. For write-heavy workloads with multiple application instances, this is a hard architectural limit.

No network access by default. SQLite is embedded in the application process. Sharing a SQLite database between multiple servers requires distributed SQLite solutions (Turso, LiteFS, Litestream).

Limited data types. SQLite uses dynamic typing — storing "hello" in an INTEGER column works. This flexibility can be a footgun.

No built-in authentication. All access control happens at the file system level.


PostgreSQL: The Developer’s Choice

PostgreSQL (Postgres) has become the default recommendation for new applications in 2026. It’s the most feature-rich open-source relational database, with unmatched support for complex queries, JSON, full-text search, and extensibility.

Core Strengths

Advanced MVCC. PostgreSQL’s Multi-Version Concurrency Control allows readers and writers to operate simultaneously without blocking each other. Readers never block writers; writers never block readers.

Rich data types. PostgreSQL supports arrays, JSON/JSONB, hstore (key-value), range types, UUID, network addresses (inet, cidr), and geometric types. MySQL has none of these natively.

-- PostgreSQL: JSONB with indexing
CREATE TABLE events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id BIGINT NOT NULL REFERENCES users(id),
    event_type TEXT NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- GIN index for JSON queries
CREATE INDEX idx_events_payload ON events USING gin(payload);

-- Query JSON efficiently
SELECT id, payload->>'action' as action, payload->'metadata'->>'ip' as ip
FROM events
WHERE event_type = 'login'
  AND payload @> '{"status": "success"}'
  AND created_at > now() - INTERVAL '24 hours';

PostgreSQL’s full-text search is production-grade for most applications, replacing the need for Elasticsearch on smaller datasets:

-- Add tsvector column
ALTER TABLE articles ADD COLUMN search_vector tsvector;

-- Populate with weighted tokens
UPDATE articles SET search_vector =
    setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
    setweight(to_tsvector('english', coalesce(summary, '')), 'B') ||
    setweight(to_tsvector('english', coalesce(body, '')), 'C');

-- GIN index for fast search
CREATE INDEX idx_articles_search ON articles USING gin(search_vector);

-- Trigger to keep it updated
CREATE TRIGGER articles_search_update
    BEFORE INSERT OR UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION
    tsvector_update_trigger(search_vector, 'pg_catalog.english', title, summary, body);

-- Full-text search query with ranking
SELECT id, title, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('english', 'postgresql & performance') query
WHERE search_vector @@ query
ORDER BY rank DESC
LIMIT 20;

Window Functions and CTEs

PostgreSQL’s window function support is among the most complete in any database:

-- Running totals, ranking, lag/lead analytics
SELECT
    user_id,
    month,
    revenue,
    SUM(revenue) OVER (PARTITION BY user_id ORDER BY month) as running_total,
    revenue - LAG(revenue, 1) OVER (PARTITION BY user_id ORDER BY month) as mom_change,
    RANK() OVER (PARTITION BY month ORDER BY revenue DESC) as monthly_rank
FROM user_revenue
WHERE year = 2026;

-- Recursive CTE for hierarchical data
WITH RECURSIVE org_chart AS (
    SELECT id, name, manager_id, 0 as depth
    FROM employees
    WHERE manager_id IS NULL  -- top level

    UNION ALL

    SELECT e.id, e.name, e.manager_id, oc.depth + 1
    FROM employees e
    JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT id, name, depth, REPEAT('  ', depth) || name as hierarchy
FROM org_chart
ORDER BY depth, name;

PostgreSQL Performance Tuning

PostgreSQL requires more tuning than MySQL or SQLite out of the box. Key settings for a 16GB RAM server:

# postgresql.conf
shared_buffers = 4GB              # 25% of RAM
effective_cache_size = 12GB       # 75% of RAM
work_mem = 64MB                   # Per sort/hash operation
maintenance_work_mem = 1GB        # For VACUUM, CREATE INDEX
wal_buffers = 64MB
max_wal_size = 4GB
checkpoint_completion_target = 0.9
random_page_cost = 1.1            # For SSD storage
effective_io_concurrency = 200    # For SSD
max_connections = 200             # Use PgBouncer for more

PostgreSQL Extensions (2026)

The extension ecosystem is a major competitive advantage:

ExtensionUse Case
pgvectorVector similarity search (AI embeddings)
pg_partmanAutomatic table partitioning
TimescaleDBTime-series data
PostGISGeographic/spatial data
pg_cronDatabase-level job scheduling
pg_stat_statementsQuery performance tracking
uuid-osspUUID generation

MySQL: The Proven Web Workhorse

MySQL powers an enormous share of the world’s web applications. WordPress, Drupal, Magento, and most LAMP-stack applications run on MySQL. It’s fast for read-heavy workloads, operationally familiar, and the hosting ecosystem is ubiquitous.

Core Architecture: InnoDB Storage Engine

Since MySQL 8.0, InnoDB is the only supported storage engine for production use. It provides ACID transactions, MVCC, and foreign key constraints:

-- MySQL 8.0+ — modern features
CREATE TABLE orders (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
    total_cents INT UNSIGNED NOT NULL,
    metadata JSON,
    created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
    updated_at DATETIME(6) NOT NULL DEFAULT NOW(6) ON UPDATE NOW(6),

    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

MySQL JSON Support

MySQL 8.0 added solid JSON support, though it lags behind PostgreSQL’s JSONB:

-- MySQL JSON operations
SELECT
    id,
    JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.source')) as source,
    JSON_UNQUOTE(metadata->'$.campaign') as campaign
FROM orders
WHERE JSON_EXTRACT(metadata, '$.source') = '"organic"'
  AND status = 'delivered';

-- JSON_TABLE — extract JSON into relational format
SELECT jt.*
FROM orders,
JSON_TABLE(metadata->'$.items', '$[*]' COLUMNS (
    product_id INT PATH '$.product_id',
    quantity INT PATH '$.quantity',
    price DECIMAL(10,2) PATH '$.price'
)) AS jt
WHERE id = 12345;

MySQL Replication: Group Replication

MySQL Group Replication (MGR) provides automatic failover with multi-primary or single-primary modes:

-- Check replication status
SELECT MEMBER_ID, MEMBER_HOST, MEMBER_STATE, MEMBER_ROLE
FROM performance_schema.replication_group_members;

-- Read from replicas in application
-- Read connection (replica)
SELECT * FROM users WHERE id = ?;  -- Routes to replica

-- Write connection (primary)
INSERT INTO users (email, name) VALUES (?, ?);  -- Routes to primary

MySQL Weaknesses vs PostgreSQL

Limited data types. No native arrays, no range types, no hstore. JSON support is functional but not as deep as PostgreSQL’s JSONB with GIN indexing.

Window functions. Available since MySQL 8.0 but less complete than PostgreSQL. Recursive CTEs work but have performance differences.

Full-text search. MySQL’s FULLTEXT search is less powerful than PostgreSQL’s tsvector. For production search, most MySQL users migrate to Elasticsearch.

DDL operations. Some ALTER TABLE operations in MySQL require full table rebuilds, causing downtime. PostgreSQL handles many DDL changes online.


Performance Benchmarks (2026)

Tests run on equivalent hardware (8 vCPU, 32GB RAM, NVMe SSD), using pgbench / sysbench with 1M row dataset.

Read Performance (SELECT with index)

Database1 Connection100 Connections500 Connections
SQLite (WAL)~120k QPS~90k QPS (read only)N/A
PostgreSQL 17~80k QPS~95k QPS~110k QPS
MySQL 8.4~95k QPS~115k QPS~130k QPS

MySQL edges out PostgreSQL for simple primary-key reads at high concurrency. This gap narrows with complex queries.

Write Performance (INSERT transactions)

DatabaseSingle RowBatch 1000
SQLite (WAL)~50k/sec~200k/sec
PostgreSQL 17~35k/sec~180k/sec
MySQL 8.4~45k/sec~220k/sec

Complex Query Performance

For analytical queries with JOINs, aggregations, and CTEs, PostgreSQL consistently outperforms MySQL:

Query TypePostgreSQLMySQL
5-table JOIN + GROUP BY45ms78ms
Window function (1M rows)120ms195ms
Recursive CTE (10 levels)8ms15ms
Full-text search (100k docs)12ms45ms
JSONB query (GIN index)3ms18ms

Which Database to Choose in 2026

Choose SQLite when:

  • Building an embedded application, desktop app, or mobile backend
  • Your app runs on a single server with < 50k req/day writes
  • You’re building edge computing services (Cloudflare D1, Turso)
  • You need zero-administration — no DBA, no server management
  • Prototyping or building internal tools where operational simplicity matters
  • You’re building read-heavy apps where writes are infrequent

Choose PostgreSQL when:

  • You need complex queries — CTEs, window functions, advanced JOINs
  • Your application requires JSONB for flexible document storage
  • You need full-text search without adding Elasticsearch
  • Building multi-tenant SaaS with row-level security
  • Your team values correctness over raw speed — PostgreSQL’s strict SQL compliance catches bugs MySQL silently swallows
  • You need vector search (pgvector) for AI/ML applications
  • Geographic/spatial data (PostGIS)

Choose MySQL when:

  • You’re maintaining an existing MySQL application (WordPress, Magento, legacy LAMP)
  • Your team has deep MySQL operational expertise and wants to leverage it
  • You need maximum read throughput for simple queries at high connection counts
  • Managed cloud databases: RDS MySQL, PlanetScale, TiDB, Vitess — mature, well-supported
  • Your queries are straightforward and you value operational familiarity
  • You’re in an environment where MySQL is the organizational standard

2026 Cloud Managed Options

SQLitePostgreSQLMySQL
AWS—RDS PG, Aurora PGRDS MySQL, Aurora MySQL
Google Cloud—Cloud SQL PG, AlloyDBCloud SQL MySQL
Azure—Flexible ServerFlexible Server
Supabase—✓—
PlanetScale——✓ (serverless MySQL)
Neon—✓ (serverless)—
Turso✓ (LibSQL)——
Fly.io✓ (LiteFS)✓—

Migration Patterns

SQLite to PostgreSQL

Most common migration path as applications grow:

# Export SQLite schema + data
sqlite3 app.db .dump > dump.sql

# Convert to PostgreSQL syntax
# - AUTOINCREMENT → SERIAL or GENERATED ALWAYS AS IDENTITY
# - DATETIME → TIMESTAMPTZ
# - INTEGER → BIGINT for primary keys
# - BLOB → BYTEA
pgloader sqlite://./app.db postgresql://localhost/appdb

MySQL to PostgreSQL

pgloader handles most conversions automatically:

pgloader mysql://user:pass@localhost/myapp postgresql://localhost/myapp

Manual fixes needed: ENUM types, stored procedures (different syntax), and AUTO_INCREMENT sequences.


Final Recommendation

In 2026, PostgreSQL is the default choice for new applications. The combination of correctness, JSON support, full-text search, extensions (especially pgvector for AI), and active development makes it the best all-around relational database.

SQLite is no longer a toy. For single-server deployments, embedded apps, edge computing, and read-heavy workloads, SQLite with WAL mode is a legitimate production database. The Turso/LibSQL ecosystem makes distributed SQLite viable.

MySQL remains the right choice for teams with existing MySQL infrastructure, WordPress/Magento deployments, or workloads where its managed cloud ecosystem (PlanetScale, Aurora MySQL) provides operational advantages.

The ranking for new projects: PostgreSQL > SQLite (single-server/edge) > MySQL (legacy/compatibility).

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.