---
title: "Postgres vs MySQL"
slug: "postgres-vs-mysql"
category: "comparisons"
tags: ["comparisons", "postgres", "mysql", "database", "sql"]
status: "stable"
last_updated: 2026-05-14
summary: "Pick Postgres for new app data unless the team already runs MySQL at Aurora or Vitess scale; the JSONB, type system, and extension story decide it."
related: ["[[backend/postgres]]", "[[backend/postgres-indexes]]", "[[backend/postgres-jsonb]]", "[[backend/prisma]]", "[[comparisons/pinecone-vs-pgvector]]", "[[comparisons/rest-vs-graphql]]"]
---

## 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 [[backend/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 `SERIALIZABLE` and `REPEATABLE READ` [[glossary/transaction-isolation|isolation levels]]; MySQL's `REPEATABLE READ` is 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-change` or `gh-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 `pgloader` for a one-shot pull; rewrite `AUTO_INCREMENT` to `IDENTITY`, `TINYINT(1)` to `boolean`, and any `ENUM` columns 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 BY` strictness, `LIMIT` syntax, `||` vs `CONCAT`) and any stored procedures.
- Application code that depends on case-insensitive default collation (MySQL `utf8mb4_0900_ai_ci`) needs `CITEXT` or `LOWER()` 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 [[comparisons/pinecone-vs-pgvector]].

## Related

- [[backend/postgres]]
- [[backend/postgres-indexes]]
- [[backend/postgres-jsonb]]
- [[backend/prisma]]
- [[comparisons/pinecone-vs-pgvector]]
