PostgreSQL vs MySQL vs MariaDB
TL;DR
For new self-hosted projects in 2026: use PostgreSQL. It has the most advanced features (JSONB, arrays, extensions, full-text search), excellent performance, true open source governance, and is the default for most modern frameworks. MySQL is a solid choice for legacy PHP applications that explicitly require it. MariaDB is the best MySQL alternative if you need MySQL compatibility without Oracle dependency — better governance, Galera replication, and drop-in compatibility.
Key Takeaways
- PostgreSQL: Most advanced SQL features, JSONB, PostGIS, true open source, best for modern apps
- MySQL 8.x: Oracle-owned (GPL), massive ecosystem, best for WordPress/legacy PHP, 8.4 LTS
- MariaDB: MySQL fork by original MySQL creator, GPL, drop-in compatible, better governance, Galera clustering
- Performance: All three handle typical self-hosted workloads comfortably; differences matter at high scale
- Docker: All have official Alpine-based images — PostgreSQL is ~78MB, MySQL ~565MB, MariaDB ~403MB
- Governance: PostgreSQL (community) > MariaDB (foundation) > MySQL (Oracle)
Quick Comparison
| Feature | PostgreSQL 16 | MySQL 8.4 | MariaDB 11.4 |
|---|---|---|---|
| License | PostgreSQL License | GPL 2.0 (dual) | GPL 2.0 |
| Governance | Community | Oracle | MariaDB Foundation |
| JSONB / JSON | ✅ JSONB (binary, indexed) | JSON (text) | JSON (text) |
| Arrays | ✅ Native | ❌ | ❌ |
| Window functions | ✅ Full | ✅ | ✅ |
| CTEs | ✅ (recursive) | ✅ | ✅ |
| Full-text search | ✅ Native + tsvector | ✅ | ✅ |
| Extensions | ✅ PostGIS, pgvector, etc. | ❌ | ❌ |
| ACID compliance | ✅ Full | ✅ InnoDB | ✅ InnoDB/Aria |
| Replication | Streaming, logical | Binary, GTID | Binary + Galera |
| Multi-master | Via Citus extension | InnoDB Cluster | ✅ Galera native |
| Default port | 5432 | 3306 | 3306 |
| Docker image size | ~78MB (alpine) | ~565MB | ~403MB |
| Best for | Modern apps, complex queries | WordPress, legacy PHP | MySQL replacement |
PostgreSQL: The Default Choice for Modern Apps
PostgreSQL is the most feature-complete open source relational database. Governed by the PostgreSQL Global Development Group (community-owned, no corporate control), it implements more of the SQL standard than any other open source database.
Why PostgreSQL for New Projects
JSONB support. Postgres stores JSON as binary (JSONB), enabling full indexing and efficient querying:
-- Store JSON documents efficiently:
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
data JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Index specific JSON fields:
CREATE INDEX idx_events_user ON events((data->>'user_id'));
-- Query JSON:
SELECT data->>'user_id', COUNT(*)
FROM events
WHERE data->>'event_type' = 'purchase'
AND (data->>'amount')::numeric > 100
GROUP BY 1;
Extensions. PostgreSQL has a rich extension ecosystem:
- pgvector: Vector similarity search for AI/ML applications
- PostGIS: Geospatial queries (closest locations, within radius, etc.)
- pg_trgm: Fuzzy text search (autocomplete, typo tolerance)
- TimescaleDB: Time-series data optimization
- Citus: Horizontal scaling / sharding
Better standards compliance. PostgreSQL implements more of the SQL standard, resulting in more portable queries.
Docker Setup
services:
postgres:
image: postgres:16-alpine # 78MB vs 565MB for full MySQL image
restart: unless-stopped
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 10s
timeout: 5s
retries: 5
Common Operations
# Connect:
docker exec -it postgres psql -U myapp
# Backup:
docker exec postgres pg_dump -U myapp myapp | gzip > backup.sql.gz
# Restore:
gunzip -c backup.sql.gz | docker exec -i postgres psql -U myapp -d myapp
MySQL 8.x: The PHP Ecosystem Standard
MySQL remains the world's most deployed relational database. Its ubiquity in the PHP ecosystem (WordPress, Drupal, Joomla, Laravel) means you'll encounter it constantly.
When to Choose MySQL
- WordPress / Drupal / Joomla: These CMS platforms are optimized for MySQL and officially recommend it
- Legacy PHP applications: Codebases written against MySQL-specific behavior
- Existing MySQL infrastructure: No reason to switch if it works
- Managed MySQL services: RDS, PlanetScale, Railway all offer managed MySQL
License Warning
MySQL's GPL 2.0 is a dual license — the Community Edition is free, but Oracle can (and does) restrict commercial use in certain ways. If you're building a commercial product and want simpler licensing: use PostgreSQL (permissive) or MariaDB (GPL without the Oracle dependency).
Docker Setup
services:
mysql:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: myapp
MYSQL_USER: myapp
MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
command: --default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
Common Operations
# Connect:
docker exec -it mysql mysql -u myapp -p myapp
# Backup (mysqldump):
docker exec mysql mysqldump -u myapp -pmypassword myapp | gzip > backup.sql.gz
# Restore:
gunzip -c backup.sql.gz | docker exec -i mysql mysql -u myapp -pmypassword myapp
MariaDB: MySQL Without Oracle
MariaDB was created in 2009 by Michael "Monty" Widenius — the original creator of MySQL — after Oracle acquired Sun Microsystems (and MySQL with it). MariaDB is a drop-in MySQL replacement with cleaner governance (MariaDB Foundation, non-profit) and some technical improvements.
Why MariaDB Instead of MySQL
No Oracle dependency. MariaDB Foundation governs the project with community input — similar to PostgreSQL's governance model.
Galera Cluster. MariaDB includes native Galera synchronous multi-master replication — all nodes accept writes, writes replicate synchronously to all nodes. MySQL's InnoDB Cluster achieves similar results but with more complexity.
# MariaDB Galera cluster (3-node):
services:
mariadb-1:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: secret
MARIADB_GALERA_CLUSTER_NAME: my-cluster
MARIADB_GALERA_NODE_ADDRESS: mariadb-1
MARIADB_GALERA_CLUSTER_ADDRESS: gcomm://mariadb-1,mariadb-2,mariadb-3
Drop-in MySQL replacement. The vast majority of MySQL applications work unmodified with MariaDB — same port (3306), same client protocol, same SQL dialect.
Better JSON in newer versions. MariaDB 11.2+ improved JSON handling significantly.
Docker Setup
services:
mariadb:
image: mariadb:11
restart: unless-stopped
environment:
MARIADB_DATABASE: myapp
MARIADB_USER: myapp
MARIADB_PASSWORD: "${MARIADB_PASSWORD}"
MARIADB_ROOT_PASSWORD: "${MARIADB_ROOT_PASSWORD}"
volumes:
- mariadb_data:/var/lib/mysql
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
Common Operations
# Connect (uses mysql client — identical to MySQL):
docker exec -it mariadb mariadb -u myapp -p myapp
# Backup:
docker exec mariadb mysqldump -u myapp -pmypassword myapp | gzip > backup.sql.gz
Performance Comparison
All three databases handle typical self-hosted workloads comfortably. Performance differences matter at scale (100K+ queries/second, billions of rows).
For typical self-hosted apps (< 10K req/s):
| Scenario | Fastest |
|---|---|
| Simple reads (SELECT by primary key) | MySQL ≈ MariaDB ≈ PostgreSQL |
| Complex joins and aggregations | PostgreSQL |
| JSON/document storage and queries | PostgreSQL (JSONB) |
| Full-text search | PostgreSQL (tsvector) ≈ MySQL |
| Write-heavy workloads | MySQL ≈ MariaDB ≈ PostgreSQL |
| Geospatial queries | PostgreSQL + PostGIS |
For an app with <1M rows and <1K concurrent connections, any of the three performs excellently.
Migration Between Databases
MySQL/MariaDB → PostgreSQL
# Use pgloader for MySQL→PostgreSQL migration:
pgloader mysql://user:pass@localhost/source_db \
postgresql://user:pass@localhost/target_db
pgloader handles type conversions, constraints, and indexes automatically.
PostgreSQL → MySQL
Harder — PostgreSQL's advanced types (JSONB, arrays, custom types) don't map directly to MySQL. Requires manual schema adaptation.
Framework Defaults
| Framework | Default Database |
|---|---|
| Django | PostgreSQL (recommended) |
| Ruby on Rails | PostgreSQL (recommended) |
| Laravel | MySQL (but PostgreSQL supported) |
| Next.js / Prisma | PostgreSQL (recommended) |
| WordPress | MySQL / MariaDB |
| Drupal | MySQL / MariaDB / PostgreSQL |
| Supabase | PostgreSQL |
| PocketBase | SQLite → PostgreSQL |
The trend in modern full-stack frameworks (Next.js, Remix, SvelteKit) is strongly toward PostgreSQL.
Decision Guide
Choose PostgreSQL if:
→ New project with no legacy constraints
→ You use Prisma, Drizzle, SQLAlchemy, or Active Record (all support it)
→ You need JSONB, arrays, or advanced SQL features
→ You want PostGIS for geospatial data
→ You want pgvector for AI/vector similarity search
→ True open source governance matters
Choose MySQL 8.x if:
→ Running WordPress, Drupal, or Joomla
→ Legacy PHP codebase with MySQL-specific queries
→ Existing MySQL infrastructure with no reason to migrate
→ Using managed services (PlanetScale, AWS RDS MySQL)
Choose MariaDB if:
→ You want MySQL compatibility without Oracle dependency
→ You need Galera synchronous multi-master replication
→ Your Linux distro ships MariaDB by default
→ Commercial product where MySQL's GPL dual-license is a concern
See all open source database tools at OSSAlt.com/categories/databases.