Overview
Pick Postgres for a new SaaS or B2B app. It has the richer type system, JSONB, partial and expression indexes, transactional DDL, and a deep extension catalog (pgvector, PostGIS, partman). Reach for MySQL only when the team is already on Aurora MySQL, runs Vitess at horizontal scale, or has operators who know MySQL replication cold. For everything else, the gap has widened in Postgres’s favor since 2020. See postgres for the in-depth rule set.
When Postgres wins
Postgres is the default for app data with mixed access patterns, analytical queries on the same store, or any structured-plus-semi-structured workload.
- JSONB with GIN indexes outperforms MySQL’s JSON for containment queries and partial-index access.
- Partial, expression, BRIN, and GiST indexes cover the long tail (case-insensitive lookups, soft-delete predicates, time-series).
- Transactional DDL: schema changes roll back on failure inside a transaction. MySQL still autocommits most DDL.
- The extension ecosystem (pgvector, PostGIS, pg_partman, TimescaleDB, Citus) lets one database cover vectors, geo, partitioning, and sharding.
- Strong defaults for
SERIALIZABLEandREPEATABLE READ; MySQL’sREPEATABLE READis the InnoDB-flavored variant with phantom-read quirks.
When MySQL wins
MySQL is the right pick in three narrow cases. Otherwise the choice is Postgres.
- Aurora MySQL on AWS for managed read replicas at high QPS where the team already pays for it.
- Vitess for horizontal sharding past what a single Postgres primary handles; companies running tens of TB on a single logical schema (Slack, Square historically).
- Pre-existing MySQL operator depth on the team: replication, ProxySQL, Percona toolkit, online schema changes via
pt-online-schema-changeorgh-ost. The operational muscle memory has real cost to rebuild on Postgres.
For new greenfield work without those constraints, MySQL’s advantages over Postgres are mostly historical.
Trade-offs at a glance
| Dimension | Postgres | MySQL |
|---|---|---|
| JSON | JSONB with GIN; query and index | JSON column; weaker indexing |
| Replication | Logical and physical; mature in 17 | Async or semi-sync; Aurora and Vitess for scale |
| DDL | Transactional; rolls back | Most DDL autocommits |
| Index types | btree, GIN, GiST, BRIN, hash, partial, expression | Mostly btree, fulltext, hash |
| Extensions | pgvector, PostGIS, partman, Citus | Limited; mostly storage engines |
| License | PostgreSQL (BSD-style) | GPL with Oracle ownership |
| Sharding | Citus or app-level; no native | Vitess is the canonical answer |
| Window functions | Full spec | Full spec since 8.0 |
| Default text type | text is unlimited and free | VARCHAR(n) defaults, charset gotchas |
Migration cost
Postgres-to-MySQL is rarely worth it; MySQL-to-Postgres is well-trodden.
- MySQL to Postgres: use
pgloaderfor a one-shot pull; rewriteAUTO_INCREMENTtoIDENTITY,TINYINT(1)toboolean, and anyENUMcolumns to lookup tables or check constraints. Plan one engineer-week per 50 tables of business logic. - Driver and ORM changes are usually small: Prisma, SQLAlchemy, Sequelize, and Drizzle all run on both. The pain is SQL dialect (
GROUP BYstrictness,LIMITsyntax,||vsCONCAT) and any stored procedures. - Application code that depends on case-insensitive default collation (MySQL
utf8mb4_0900_ai_ci) needsCITEXTorLOWER()indexes on Postgres.
Recommendation
- New SaaS or B2B app: Postgres. No exceptions worth listing.
- Hot path needs horizontal sharding past 5 TB on a single logical schema: MySQL with Vitess, or Postgres with Citus if the team prefers Postgres.
- AWS-only stack with no infra team: Aurora Postgres beats Aurora MySQL on feature surface; both are fine.
- Existing MySQL fleet with ops depth: stay on MySQL until a project genuinely needs JSONB, pgvector, or PostGIS, then port that workload.
- Vector search alongside relational data: Postgres with pgvector; see pinecone-vs-pgvector.