Which Relational Database Should I Pick?
You're starting a new project and you need a relational database. That much is clear. You want SQL, ACID transactions, foreign keys, the whole deal. But then you open a browser tab and fall into an hour of Reddit threads, each one insisting that their pick is the only serious option and everything else is a toy.
Here's the honest version: PostgreSQL, MySQL, MariaDB, and CockroachDB are all production-grade. They all handle far more traffic than your app will see for years. The differences are real, but they're about tradeoffs, not quality. This post walks through each one so you can pick the right fit for your situation and get back to building your app instead of reading arguments on the internet.
Contents
- Quick Comparison
- PostgreSQL: The Generalist
- MySQL: The Workhorse
- MariaDB: MySQL With Extras
- CockroachDB: Distributed PostgreSQL
- Side-by-Side: Same Table, Four Databases
- Decision Flowchart
- Further Reading
Quick Comparison
| PostgreSQL | MySQL | MariaDB | CockroachDB | |
|---|---|---|---|---|
| License | PostgreSQL (permissive) | GPL v2 (Oracle) | GPL v2 (community) | BSL / CockroachDB Community |
| Default Isolation | Read Committed | Repeatable Read | Repeatable Read | Serializable |
| JSON Support | jsonb (binary, indexed) | JSON (validated, partial indexing) | JSON (alias for LONGTEXT) | JSONB (same as PostgreSQL) |
| Extensions | Rich ecosystem (PostGIS, pg_vector, pg_trgm, hundreds more) | Plugins (limited) | Plugins + storage engines | None (features are built-in) |
| Replication | Streaming (physical) + logical | Binary log (async/semi-sync/group) | Binary log + Galera Cluster | Automatic Raft consensus |
| Scaling Story | Vertical + read replicas (Citus for horizontal) | Vertical + read replicas + InnoDB Cluster | Vertical + read replicas + Galera | Horizontal by default, automatic sharding |
| Wire Protocol | PostgreSQL | MySQL | MySQL (compatible) | PostgreSQL (compatible) |
The wire protocol row matters more than you might think. CockroachDB speaks PostgreSQL's protocol, so any pg driver, ORM, or tool that works with PostgreSQL works with CockroachDB. MariaDB speaks MySQL's protocol, so mysql2 drivers and MySQL tools work with both.
PostgreSQL: The Generalist
The pitch: PostgreSQL does everything well, and most things better than the alternatives.
It has the strongest extension ecosystem of any relational database. Need geospatial queries? PostGIS. Vector similarity search? pgvector. Full-text search? Built in. Arrays, hstore, range types, custom domains, CTEs, window functions, lateral joins. All there, all mature, all well-documented.
PostgreSQL's jsonb type is the gold standard for relational JSON support. Binary format, GIN indexing, containment queries, partial updates. If you're mixing structured and semi-structured data in the same database (which most applications do eventually), PostgreSQL handles it gracefully.
Default isolation level is Read Committed: good balance of correctness and performance for typical web applications. Need stricter isolation? Bump individual transactions to Serializable.
Best for: Complex queries, mixed data types, applications that might need extensions later, teams that want one database for everything.
Tradeoffs: Operations are slightly more involved than MySQL for simple deployments. Horizontal scaling requires external tooling (Citus, read replicas, or switching to CockroachDB).
Spin up a local PostgreSQL instance with SpinDB:
spindb create pg1 --startPostgreSQL is the default engine, so you don't even need the -e flag. That tells you something about where the industry has landed.
MySQL: The Workhorse
The pitch: MySQL is the most deployed relational database in the world, and it got there by being fast, simple, and reliable.
For read-heavy web applications, MySQL is excellent. InnoDB is optimized for high-concurrency read workloads, and replication setup is one of the simplest in the industry. WordPress, Shopify, GitHub, and Airbnb all run on MySQL. The ecosystem of tools, hosting providers, and developer knowledge is unmatched.
MySQL's operational simplicity is underrated. Setting up replication is straightforward. Backups are well-understood. Every managed database provider on the planet supports it, and finding a DBA who knows MySQL well is easier than for any other database.
JSON support in 8.0+ is solid. Validates on insert, supports indexing via generated columns. Not as flexible as PostgreSQL's jsonb, but it does the job for most use cases.
Best for: Read-heavy web applications, teams that want the simplest possible operations, projects where framework and hosting compatibility matter most.
Tradeoffs: Fewer built-in data types than PostgreSQL. The extension ecosystem is limited. Oracle owns it, which makes some people nervous about long-term direction, though the product itself remains excellent.
Spin up a local MySQL instance:
spindb create mysql1 -e mysql --startMariaDB: MySQL With Extras
The pitch: Everything MySQL does, plus features MySQL doesn't have, without Oracle in the picture.
MariaDB was forked from MySQL in 2010 by MySQL's original creator. For most practical purposes, it's a drop-in replacement: same SQL syntax, same wire protocol, same mysql2 driver. If your application runs on MySQL, it almost certainly runs on MariaDB without changes.
MariaDB has kept adding features MySQL hasn't matched. Sequences give you named, reusable auto-increment generators. System-versioned (temporal) tables let you query historical data at any point in time with FOR SYSTEM_TIME syntax, useful for audit trails or debugging. Oracle compatibility mode helps if you're migrating from Oracle.
The other differentiator is governance. MariaDB is maintained by the MariaDB Foundation and developed in the open. If the Oracle question matters to your organization, MariaDB removes it entirely.
Best for: Teams that want MySQL compatibility plus features like sequences and temporal tables, organizations that prefer community-governed open source.
Tradeoffs: Smaller community and ecosystem than MySQL. Some MySQL-specific features (like MySQL's CHECK constraints in older versions, or MySQL Shell) don't carry over. And while MariaDB tracks MySQL compatibility, the two have diverged enough that "drop-in replacement" deserves an asterisk for edge cases.
Spin up a local MariaDB instance:
spindb create maria1 -e mariadb --startCockroachDB: Distributed PostgreSQL
The pitch: Write standard PostgreSQL SQL. Get automatic replication, sharding, and multi-region support.
CockroachDB is the answer to "I love PostgreSQL but I need horizontal scaling." It speaks the PostgreSQL wire protocol, so you connect with the same pg driver, use the same ORMs, and write the same SQL. Under the hood, data is automatically replicated across nodes using Raft consensus, sharded by primary key ranges, and rebalanced as nodes are added or removed.
Default isolation is Serializable, the strictest possible. No phantom reads, no write skew, none of the subtle concurrency bugs that plague weaker isolation levels. For many applications, that's worth the slight performance overhead.
Node goes down? Queries keep working. Add a node? Data rebalances automatically. Need data close to users in Frankfurt and Tokyo? Configure locality constraints and CockroachDB handles the rest. Zero-downtime schema changes are built in.
Best for: Applications that need horizontal scaling, multi-region deployments, zero-downtime migrations, or strong consistency guarantees across a distributed system.
Tradeoffs: Single-node performance is lower than PostgreSQL for the same hardware because CockroachDB pays the cost of distributed consensus even on one node. No extension ecosystem. Some PostgreSQL features are unsupported (certain PL/pgSQL constructs, some system catalogs, advisory locks). The BSL license means the source is available but it's not open source in the traditional sense.
Spin up a local CockroachDB instance:
spindb create crdb1 -e cockroachdb --startSide-by-Side: Same Table, Four Databases
SQL is SQL. Here's the same table and query across all four, each running locally via SpinDB.
Create the instances
npm i -g spindb
spindb create pg1 --start
spindb create mysql1 -e mysql --start
spindb create maria1 -e mariadb --start
spindb create crdb1 -e cockroachdb --startFour database servers running on your machine. Get connection strings:
spindb url pg1
spindb url mysql1
spindb url maria1
spindb url crdb1The same table in all four
PostgreSQL and CockroachDB (both use the pg driver):
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price NUMERIC(10,2) NOT NULL,
category VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO products (name, price, category) VALUES
('Widget A', 29.99, 'hardware'),
('Widget B', 49.99, 'hardware'),
('Service Plan', 9.99, 'software');MySQL and MariaDB (both use the mysql2 driver):
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
category VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO products (name, price, category) VALUES
('Widget A', 29.99, 'hardware'),
('Widget B', 49.99, 'hardware'),
('Service Plan', 9.99, 'software');The only difference is SERIAL vs AUTO_INCREMENT and NUMERIC vs DECIMAL (which are actually synonyms in most of these databases). The INSERT and the table structure are identical.
The same query in all four
This query works on all four databases without modification:
SELECT
category,
COUNT(*) AS product_count,
AVG(price) AS avg_price
FROM products
GROUP BY category
ORDER BY avg_price DESC; category | product_count | avg_price
-----------+---------------+-----------
hardware | 2 | 39.99
software | 1 | 9.99JOINs, subqueries, CTEs, window functions, and most standard SQL features work identically across all four. The differences show up in database-specific features like PostgreSQL's jsonb operators, MySQL's FULLTEXT indexes, MariaDB's temporal tables, or CockroachDB's SHOW RANGES.
Clean up
When you're done experimenting:
spindb stop pg1 mysql1 maria1 crdb1Or if you want to remove them entirely:
spindb destroy pg1 mysql1 maria1 crdb1Decision Flowchart
Not a diagram. Just a series of questions. Start at the top and follow the first one that applies.
Need horizontal scaling, multi-region replication, or zero-downtime deployments? CockroachDB. It's the only one here that does distributed SQL natively. You write PostgreSQL-compatible SQL and get those capabilities without bolting on external tooling.
Migrating from MySQL but want community governance and features like temporal tables or sequences? MariaDB. Closest thing to "MySQL but better" for most workloads, and you won't have to change your application code.
Building a read-heavy web app where operational simplicity and ecosystem breadth are the top priorities? MySQL. The tooling, hosting, and community support are the largest of any relational database. Known quantity.
No strong constraint pushing you toward any of the above? PostgreSQL. It handles the widest range of use cases well, has the strongest extension ecosystem, and the industry has clearly converged on it as the default. You're unlikely to hit a wall where you wish you'd picked something else.
If someone asked me "just tell me what to use," my answer is PostgreSQL. Not because the others are worse, but because it's the safest default. Complex queries, mixed data types, evolving requirements: PostgreSQL handles all of them better than anything else on this list. You can always reach for CockroachDB later if you outgrow a single node.
Further Reading
Each database here has a dedicated getting-started post with a full TypeScript project you can run locally:
- PostgreSQL: What's New in PostgreSQL 18 covers the latest features with runnable examples
- MySQL: Getting Started with MySQL builds a blog platform with JSON columns, full-text search, and CTEs
- MariaDB: Getting Started with MariaDB builds an inventory system showcasing sequences and temporal tables
- CockroachDB: Getting Started with CockroachDB builds a distributed orders system with time-travel queries
All four run locally with SpinDB and are available as managed instances on Layerbase Cloud.