Relational database comparison

PostgreSQL vs MySQL

The two most popular open-source relational databases. Both are battle-tested and widely supported in the cloud — but they differ in architecture, features, and philosophy. Choosing between them comes down to feature richness, performance profile, and ecosystem preference.

TL;DR

  • PostgreSQL — advanced types (JSONB, arrays, hstore), window functions, tablespaces, a rich extension ecosystem (PostGIS, pgvector, TimescaleDB), strong SQL standards compliance, and a fully permissive PostgreSQL Licence. The default choice for new projects in the wider developer community.
  • MySQL — simpler initial setup, fast on straightforward read-heavy workloads, the default for LAMP stacks and CMS platforms (WordPress, Drupal), dual GPL/commercial licence. InnoDB is the standard storage engine; performance_schema provides deep runtime diagnostics.
  • Both are excellent. For most greenfield applications today, PostgreSQL is the community default. MySQL remains dominant where a LAMP ecosystem, managed hosting, or existing team expertise drives the decision.

Side-by-side comparison

AspectPostgreSQLMySQL
Concurrency modelMVCC — readers never block writersInnoDB also uses MVCC; row-level locking
JSON supportJSONB — binary, indexable, 50+ operatorsJSON column since 5.7 — text-stored, fewer operators
Full-text searchBuilt-in tsvector/tsquery with ranking & stemmingFULLTEXT indexes on InnoDB/MyISAM; basic ranking
Window functionsFull SQL:2003 support since v8.4Added in MySQL 8.0
ExtensionsPostGIS, pgvector, TimescaleDB, pg_cron, pg_trgm…Plugin architecture; smaller ecosystem
Storage enginesSingle storage layer (heap + WAL)Pluggable: InnoDB (default), MyISAM, Memory…
ReplicationStreaming WAL-based + logical replicationBinlog replication, GTID-based; Group Replication
TablespacesSupported — map tables to different disk volumesInnoDB tablespaces supported; per-table files
Diagnosticspg_stat_* system viewsperformance_schema + sys schema
LicensingPostgreSQL Licence — permissive, no restrictionsGPL (Community) or paid commercial (Enterprise)

What is PostgreSQL?

PostgreSQL (often called Postgres) is an open-source object-relational database system with over 35 years of development. It began as the POSTGRES project at UC Berkeley in 1986 and became fully open-source in 1996. It is governed by the PostgreSQL Global Development Group and released under the permissive PostgreSQL Licence, which imposes no restrictions on commercial use or distribution.

Postgres uses MVCC (Multi-Version Concurrency Control) throughout its storage engine. Every row written creates a new version; old versions are retained until a VACUUM pass reclaims space. This means readers never block writers and writers never block readers — a significant advantage for mixed read/write workloads.

Its extension system is one of its most valued features. Extensions such as PostGIS (geospatial), pgvector (vector similarity search for AI applications), TimescaleDB (time-series), and pg_cron (scheduled jobs) can be installed into the database with CREATE EXTENSION. This makes Postgres a foundation for specialised workloads without leaving the relational model.

PostgreSQL's JSONB type stores JSON in a parsed binary format. Unlike plain text JSON columns, JSONB supports GIN indexes, key-existence operators (?), containment operators (@>), and path queries — making it competitive with document databases for many semi-structured data use cases. This is particularly relevant when teams debate SQL vs NoSQL for new projects.

What is MySQL?

MySQL is an open-source relational database originally developed by MySQL AB in 1995. It was acquired by Sun Microsystems in 2008 and then by Oracle in 2010. The Community Edition is released under the GPL (GNU General Public Licence); the Enterprise Edition requires a commercial licence from Oracle. A popular fork, MariaDB, was created by MySQL's original authors after the Oracle acquisition and is largely compatible.

MySQL's architecture supports pluggable storage engines. The default and most important is InnoDB, which provides ACID transactions, row-level locking, foreign key enforcement, and its own MVCC implementation. Older engines such as MyISAM lack transactions but were historically faster for read-only workloads. The Memory engine stores data in RAM for temporary tables.

performance_schema is a MySQL facility that instruments the server's internal execution events — query timing, lock waits, I/O, and memory usage. It is MySQL's primary tool for performance analysis, complemented by the sys schema, which provides human-readable views over performance_schema data.

MySQL's binary log (binlog) records every change made to the database and drives replication. GTID-based replication assigns a globally unique identifier to each transaction, simplifying failover and topology changes. MySQL 8.0 also introduced Group Replication for synchronous multi-primary clusters.

Key differences in depth

Licensing: PostgreSQL Licence vs GPL

The PostgreSQL Licence is a permissive open-source licence similar to the MIT or BSD licences. You can use, modify, and distribute PostgreSQL in proprietary products without obligation to release your source code. This makes it particularly attractive for SaaS companies and cloud providers.

MySQL Community Edition is under the GPL version 2. The GPL's "copyleft" provisions mean that if you distribute software that links against MySQL, you must either release that software under the GPL or purchase a commercial licence from Oracle. For internal use — the most common scenario — this is not an issue. However, it has influenced some companies to prefer PostgreSQL or to use MariaDB as a drop-in replacement.

Full-text search

PostgreSQL's full-text search is based on tsvector (a sorted list of normalised lexemes) and tsquery (a search expression). The to_tsvector() and to_tsquery() functions handle stemming, stop words, and ranking via ts_rank(). GIN indexes make queries fast. It handles multiple languages via configurable dictionaries.

MySQL provides FULLTEXT indexes on InnoDB tables (since MySQL 5.6). Queries use MATCH … AGAINST syntax in BOOLEAN or NATURAL LANGUAGE mode. It is serviceable for basic search but lacks the linguistic depth and composability of PostgreSQL's approach. For production search, both databases are often complemented by Elasticsearch anyway.

Tablespaces

Both databases support tablespaces — the ability to map database objects to specific directories or disk volumes. In PostgreSQL, CREATE TABLESPACE fast_ssd LOCATION '/mnt/ssd' lets you place hot tables on faster storage. InnoDB tablespaces work similarly, with per-table files (innodb_file_per_table) or shared tablespace files.

Code side-by-side

Query JSON data stored in a metadata column:

PostgreSQL (JSONB)

-- Find products where metadata->>'category' = 'books'
SELECT id, name, metadata->>'price' AS price
FROM products
WHERE metadata @> '{"category": "books"}'
ORDER BY (metadata->>'price')::numeric DESC;

-- GIN index for fast key lookups
CREATE INDEX idx_products_meta
  ON products USING GIN (metadata);

MySQL (JSON column)

-- Find products where category = 'books'
SELECT id, name,
  JSON_EXTRACT(metadata, '$.price') AS price
FROM products
WHERE JSON_EXTRACT(metadata, '$.category') = 'books'
ORDER BY JSON_EXTRACT(metadata, '$.price') DESC;

-- Functional index on JSON path (MySQL 8.0+)
ALTER TABLE products
  ADD INDEX idx_category
    ((CAST(metadata->>'$.category' AS CHAR(50))));

Window function: rank users by order total per region:

PostgreSQL

SELECT user_id, region, total,
  RANK() OVER (
    PARTITION BY region
    ORDER BY total DESC
  ) AS regional_rank
FROM orders;

MySQL 8.0+

SELECT user_id, region, total,
  RANK() OVER (
    PARTITION BY region
    ORDER BY total DESC
  ) AS regional_rank
FROM orders;

Window function syntax is identical — MySQL 8.0 added full SQL:2003 window function support, catching up with PostgreSQL.

How engineers talk about PostgreSQL vs MySQL

Understanding the vocabulary used in code reviews, architecture discussions, and incident retrospectives will help you follow and contribute to database conversations in English.

PostgreSQL phrases

  • "We store metadata as JSONB so we can query inside it without a schema migration."
  • "I added a GIN index on the JSONB column — key lookups are now sub-millisecond."
  • "The query uses a window function: ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at)."
  • "We set up logical replication to stream changes to the analytics replica without downtime."
  • "Vacuum is reclaiming dead tuples left by MVCC — that's expected on a write-heavy table."
  • "We installed the pgvector extension to store and query embeddings alongside relational data."
  • "The tablespace is pointed at the NVMe volume — index scans are about 3× faster."

MySQL phrases

  • "The site runs on a LAMP stack — Linux, Apache, MySQL, PHP — so MySQL was the obvious choice."
  • "We enabled GTID-based replication so failover is just a CHANGE MASTER TO command."
  • "Check performance_schema — there's a lock wait showing up on the users table."
  • "We switched every table to InnoDB years ago — MyISAM has no transactions or foreign keys."
  • "PlanetScale uses MySQL under the hood with Vitess sharding for horizontal scale."
  • "The binary log is enabled for point-in-time recovery and replication."
  • "Strict mode was off — MySQL was silently truncating strings to fit the column."

Decision guide: when to choose which

  • Complex queries, JOINs, analytical workloads, window functions → PostgreSQL
  • Need JSONB, arrays, hstore, or custom data types → PostgreSQL
  • Geospatial data (PostGIS) or vector similarity search (pgvector for AI) → PostgreSQL
  • Licensing must be fully permissive (SaaS, commercial distribution) → PostgreSQL
  • Rich extension ecosystem required → PostgreSQL
  • Existing WordPress, Drupal, or LAMP-based stack → MySQL
  • Team deeply experienced with MySQL and no compelling feature gap → MySQL (if it works, don't migrate)
  • Need deep runtime diagnostics without external tooling → MySQL (performance_schema)
  • Greenfield project with no constraints → PostgreSQL (community default)

If you are also deciding between a relational and a non-relational approach, see SQL vs NoSQL. For database deployment in containers and orchestration, see Docker vs Kubernetes.

Key vocabulary

MVCC (Multi-Version Concurrency Control)
A concurrency technique where each transaction reads from a snapshot of the database, allowing reads and writes to proceed in parallel without locking each other out. Both PostgreSQL and MySQL's InnoDB use MVCC.
JSONB
PostgreSQL's binary JSON type. Stored in a parsed, decomposed format that supports GIN indexing, containment queries (@>), and key-existence checks (?). More powerful than plain JSON text columns.
Extension
A PostgreSQL module installed with CREATE EXTENSION that adds new types, functions, index methods, or entire subsystems. PostGIS, pgvector, TimescaleDB, and pg_trgm are common examples.
Tablespace
A named location on disk where database objects (tables, indexes) are stored. Allows placing hot or large tables on specific storage volumes for performance or capacity reasons.
InnoDB
MySQL's default and recommended storage engine. Provides ACID transactions, row-level locking, foreign key enforcement, and MVCC. Replaced the older MyISAM engine (which lacked transactions) as the MySQL default in version 5.5.
performance_schema
A MySQL instrumentation layer that records runtime metrics — query latency, lock waits, memory usage, I/O — into in-memory tables. The sys schema provides readable summary views over this data.
WAL (Write-Ahead Log)
PostgreSQL's transaction log. All changes are written to the WAL before being applied to data files, ensuring durability and enabling streaming replication to standby servers.
GTID (Global Transaction ID)
MySQL's mechanism for assigning a unique identifier to every committed transaction across the replication topology. Makes failover and replica re-pointing simpler and safer.
Full-text search
The ability to search natural language text efficiently. PostgreSQL uses tsvector/tsquery with stemming and ranking. MySQL uses FULLTEXT indexes with MATCH … AGAINST syntax.
Window function
An SQL function that operates across a set of rows related to the current row without collapsing them into a single output row. Examples: RANK(), LAG(), SUM() OVER (…). Both databases support them fully in current versions.

Frequently asked questions

What is the main practical difference between PostgreSQL and MySQL?

Both are mature open-source relational databases. PostgreSQL is often called the most standards-compliant and feature-rich option — it has advanced types (arrays, hstore, JSONB), window functions, powerful extensions, and uses MVCC for concurrency. MySQL (and its fork MariaDB) is known for simplicity, fast read-heavy workloads, and its enormous hosting ecosystem, particularly in LAMP stacks.

What is MVCC and does MySQL support it?

MVCC (Multi-Version Concurrency Control) is a technique where each transaction sees a snapshot of the database at the time it started. Readers do not block writers, and writers do not block readers. PostgreSQL uses MVCC throughout. MySQL's InnoDB storage engine also implements MVCC, but PostgreSQL's implementation is generally considered more thorough and better integrated with the planner.

Does PostgreSQL support JSON better than MySQL?

Yes. PostgreSQL's JSONB type stores JSON in a binary, parsed format that supports GIN indexing, efficient key lookups, and a rich set of operators and functions. MySQL has a JSON column type added in 5.7, but its capabilities are more limited and it lacks binary storage. If you need to query inside JSON frequently, PostgreSQL is the stronger choice.