DevLift
Back to Blog

PostgreSQL vs MySQL — Two SQL Giants, Surprisingly Different Tradeoffs

PostgreSQL and MySQL look similar on the surface. Choose the wrong one and you'll spend months fighting your database instead of building your product.

Admin
February 2, 202610 min read2 views

PostgreSQL vs MySQL — Two SQL Giants, Surprisingly Different Tradeoffs

You're starting a new project. You pick PostgreSQL because it's what your last company used. Or you pick MySQL because it's what the tutorial used. Six months later you're fighting autovacuum bloat, or discovering that MySQL silently truncated your data three months ago and nobody noticed. Both databases are excellent — and both have landmines that catch teams off guard.

The irony is that PostgreSQL and MySQL look almost identical for basic CRUD. They both speak SQL, both use tables and indexes, both have transactions. The differences only emerge when you need JSON queries with nested filtering, or geospatial data, or you want to distribute your proprietary software without GPL compliance headaches. By then, migrating is expensive.

Here's the honest comparison.

Quick Decision Matrix

If you need...Choose
Complex queries, analytics, or aggregationsPostgreSQL
JSON storage with deep query/index supportPostgreSQL
Geospatial data (PostGIS)PostgreSQL
AI/ML vector embeddings (pgvector)PostgreSQL
Embed in proprietary/commercial softwarePostgreSQL
Mature active-active multi-primary replicationMySQL
Simple LAMP stack or WordPress/DrupalMySQL
Legacy teams with MySQL DBA expertiseMySQL
Read-heavy workload, simple queriesEither (MySQL slight edge)
Write-heavy, high-concurrency OLTPPostgreSQL

30-Second Summary

PostgreSQL is a full-featured, standards-compliant relational database that treats SQL like a first-class language. It has a rich extension ecosystem, native JSONB support with GIN indexing, excellent support for complex queries and analytics, and a permissive BSD-style license. It's the most-used database in the Stack Overflow Developer Survey — 55.6% of all respondents and 58.2% of professional developers in 2025, up from 48.7% the year before. (Careful with this stat: "most used" is a different question from Stack Overflow's "admired" and "desired" metrics, and the survey retired "most loved" after 2022.) The cost: it has real operational complexity — autovacuum, connection management, and configuration all need tuning in production.

MySQL is the world's most deployed open-source database, the M in the LAMP stack, and the default choice for the WordPress/Drupal ecosystem. It's operationally simpler for read-heavy web workloads, has mature multi-primary replication via Group Replication, and is deeply familiar to a generation of web developers. The cost: historically weaker SQL standards compliance, a GPL license that complicates proprietary use, and a limited extension model.

Developer Experience and Learning Curve

Winner: MySQL for newcomers, PostgreSQL for developers who stay.

MySQL is easier to install and get running. The defaults work reasonably well for a small web app. The LAMP stack documentation is decades deep. If you're building a WordPress plugin or a Laravel app, MySQL is the path of least resistance.

PostgreSQL has more configuration surface area — postgresql.conf, pg_hba.conf, autovacuum settings — and its process-per-connection model means you'll need PgBouncer in production as soon as you have meaningful traffic. But once you're past that initial curve, PostgreSQL's developer experience is better for complex work. The psql CLI is excellent. Error messages are precise. The documentation is thorough and accurate.

The productivity gap becomes obvious when you need something beyond basic CRUD. PostgreSQL's EXPLAIN ANALYZE output is detailed and actionable. Its array types, JSONB operators, and window function support let you express complex queries in SQL rather than in application code.

Performance Characteristics

Winner: Context-dependent — PostgreSQL for complex queries, MySQL for simple reads at extreme scale.

For most applications, the performance difference is irrelevant — both databases execute simple SELECT queries in under 1ms. The divergence appears at scale and complexity.

For complex analytical queries (multi-table joins, aggregations, window functions), PostgreSQL's cost-based planner is generally better at choosing join strategies than MySQL's.

If you want a citable number, the closest thing is Kamal et al., "A Performance Benchmark for the PostgreSQL and MySQL Databases" (Future Internet 16(10):382, October 2024), which measured PostgreSQL at 0.6–0.8 ms against MySQL at 9–12 ms on a 1M-record dataset. Two caveats that matter more than the headline: those figures are from the paper's simple select tests, not its complex ones (the concurrent-query results were 0.7–0.9 ms vs 7–13 ms), and it's a single academic study on keystroke-biometric data for continuous-authentication workloads — not a general-purpose OLTP benchmark. Don't carry a 13x ratio into a capacity plan.

For high-concurrency writes, PostgreSQL's MVCC lets multiple writers proceed with minimal locking and readers never block writers. MySQL's InnoDB also uses MVCC. The tradeoff is real but runs both ways: PostgreSQL's approach leaves dead tuples behind, which is why autovacuum exists and why ignoring it hurts.

The most-cited counter-example is Uber's 2016 migration from Postgres to MySQL, and it's worth getting the reasoning right because it gets garbled constantly. Uber's complaint was write amplification arising from Postgres's on-disk row format: an update writes a new physical tuple, and because indexes point at physical tuple locations, every non-HOT update must write a new entry into every index, which then amplifies into the WAL and the replication stream. InnoDB avoids it because secondary indexes reference the primary key, so only indexes on modified columns need touching. Difficulty with major-version upgrades was a separate item on Uber's list, not the cause of the write amplification — the two get conflated constantly. Both critiques are real; both describe Uber's specific write-heavy workload in 2016.

complex-query-postgres.sql
-- PostgreSQL: complex analytics query with window function
SELECT
  user_id,
  order_total,
  SUM(order_total) OVER (
    PARTITION BY user_id
    ORDER BY created_at
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total,
  RANK() OVER (PARTITION BY region ORDER BY order_total DESC) AS regional_rank
FROM orders
WHERE created_at >= NOW() - INTERVAL '30 days';
complex-query-mysql.sql
-- MySQL 8.x: same query works, but query planning is less sophisticated
-- For complex joins, expect significantly higher execution times
SELECT
  user_id,
  order_total,
  SUM(order_total) OVER (
    PARTITION BY user_id
    ORDER BY created_at
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total,
  RANK() OVER (PARTITION BY region ORDER BY order_total DESC) AS regional_rank
FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY);

JSON and Semi-Structured Data

Winner: PostgreSQL — it's not close.

PostgreSQL's JSONB type stores JSON in a binary format and supports GIN indexes for querying nested fields without full-table scans. You can index a specific nested key, use containment operators (@>), and query array elements efficiently.

MySQL has a JSON type since 5.7, with path operators (->>, JSON_EXTRACT). It also stores JSON in an optimized binary format — the manual is explicit that documents are "converted to an internal format that permits quick read access to document elements" and that the server can "look up subobjects or nested values directly by key or array index." Anyone telling you MySQL stores JSON as text is a decade out of date.

The real gap is indexing ergonomics, not storage. PostgreSQL gives you a single GIN index over the whole document and operators that use it. MySQL needs you to decide up front what you'll query and materialize it — either a generated column plus a regular index, or a multi-valued index for array membership.

jsonb-postgres.sql
-- PostgreSQL: index a nested JSON field directly
CREATE INDEX idx_user_metadata_plan
  ON users USING GIN ((metadata->'subscription'));
 
-- Query users on a specific plan with index support
SELECT id, email
FROM users
WHERE metadata @> '{"subscription": {"plan": "pro"}}';
 
-- Containment check on an array of tags
SELECT * FROM articles
WHERE tags @> '["postgresql", "performance"]'::jsonb;
json-mysql.sql
-- MySQL: requires a generated column + index for equivalent performance
ALTER TABLE users
  ADD COLUMN subscription_plan VARCHAR(50)
  GENERATED ALWAYS AS (metadata->>'$.subscription.plan') STORED;
 
CREATE INDEX idx_subscription_plan ON users(subscription_plan);
 
-- Query now uses the generated column index
SELECT id, email FROM users
WHERE subscription_plan = 'pro';
 
-- Array containment IS possible, via a multi-valued index (8.0.17+)
ALTER TABLE articles
  ADD INDEX idx_tags ( (CAST(tags AS CHAR(50) ARRAY)) );
 
SELECT * FROM articles
WHERE 'postgresql' MEMBER OF (tags);          -- uses idx_tags
 
SELECT * FROM articles
WHERE JSON_CONTAINS(tags, '["postgresql","performance"]');

So MySQL does have containment — JSON_CONTAINS(), JSON_OVERLAPS() and the MEMBER OF() operator, all index-backed if you've declared a multi-valued index. What it lacks is PostgreSQL's ability to index the whole document once and have arbitrary containment queries use that index without you anticipating them.

💡

Key Insight — If your schema has semi-structured data (user preferences, feature flags, event properties), PostgreSQL's JSONB with GIN indexes can replace a separate document store entirely. MySQL's JSON support often leads teams to add MongoDB or Redis for JSON-heavy queries anyway.

Extension Ecosystem

Winner: PostgreSQL — MySQL has no equivalent.

PostgreSQL's extension model is a genuine superpower. Extensions are first-class citizens — they can add new data types, operators, index types, and background workers. The ecosystem includes:

  • PostGIS — full geospatial database with geometry types, spatial indexes, and hundreds of functions. Used in production at Uber, OpenStreetMap, and every serious GIS application.
  • pgvector — native vector similarity search for AI/ML embeddings. Supabase, Neon, and most PostgreSQL-as-a-service providers now offer this out of the box.
  • TimescaleDB — turns PostgreSQL into a time-series database with automatic partitioning and compression.
  • pg_partman — automated table partitioning management.
  • Citus — distributed PostgreSQL, used by Microsoft Azure.

MySQL has plugins (InnoDB, Group Replication) but no equivalent ecosystem. If you need geospatial, you're relying on MySQL's built-in spatial types, which are significantly less capable than PostGIS.

Replication and High Availability

Winner: Split — MySQL for active-active, PostgreSQL for modern logical replication.

MySQL's replication model is battle-tested for web applications. GTID-based replication makes failover deterministic — no manual binlog position tracking. MySQL Group Replication supports multi-primary active-active setups natively, which is genuinely useful for globally distributed write workloads.

PostgreSQL offers two replication modes. Streaming (physical) replication sends WAL bytes to create byte-perfect standby replicas — simple and reliable for read replicas. Logical replication replicates row-level changes, supports cross-major-version replication, and allows selective table replication to non-PostgreSQL targets.

Two releases worth knowing about here. PostgreSQL 17 added failover-capable logical slots (the failover argument on slot creation, pg_sync_replication_slots(), the sync_replication_slots GUC) plus the pg_createsubscriber tool — before that, carrying logical replication across a primary failover meant manual intervention. PostgreSQL 18, released September 2025 and the current major, added logical replication of generated columns, conflict logging in pg_stat_subscription_stats, parallel streaming as the CREATE SUBSCRIPTION default, and automatic dropping of idle replication slots.

⚠️

Common Trap — PostgreSQL's process-per-connection model means 500 simultaneous connections spawn 500 OS processes. This crushes performance. Always run PgBouncer or Pgpool-II in production. MySQL uses threads (not processes), so connection overhead is much lower.

SQL Standards Compliance

Winner: PostgreSQL.

PostgreSQL has consistently prioritized SQL standards compliance. MySQL has a history of non-standard behavior — silent data truncation, non-standard GROUP BY semantics (resolved in MySQL 8.0 with ONLY_FULL_GROUP_BY mode), and case-insensitive collation defaults that surprised teams moving data between environments.

MySQL 8.0 fixed most of the infamous compliance issues, but PostgreSQL remains stricter by default. This matters for teams that write portable SQL or use database-agnostic ORMs.

Licensing

Winner: PostgreSQL for most businesses building products.

This is often the deciding factor that nobody thinks about until it's a legal problem.

PostgreSQL License is essentially MIT/BSD — permissive, business-friendly, no copyleft. You can embed PostgreSQL in a proprietary product and ship it without licensing concerns. No CLA required.

MySQL is dual-licensed: GPL v2 for open-source use and a commercial Oracle license for proprietary use. If you distribute software that bundles MySQL, GPL v2 requires your software to also be GPL-licensed. SaaS web applications are typically fine (the GPL's distribution clause doesn't apply to network access), but desktop software, embedded devices, or installable server software that includes MySQL requires either making your code GPL or paying Oracle for a commercial license.

This is part of why MariaDB exists as a community-governed fork — though note MariaDB Server is itself GPLv2, not permissively licensed. Forking Oracle's stewardship is not the same as escaping the copyleft. If permissive licensing is the requirement, PostgreSQL is the answer, not MariaDB.

⚠️

Version housekeeping, because MySQL's numbering got strange. MySQL 8.0 reached end of life on 30 April 2026 — no further community security or bug fixes, so if you're still on it, that's now the most urgent item in this article for you. The supported LTS lines are 8.4 and 9.7, and Oracle moved the Innovation series to calendar versioning (MySQL 26.7 shipped July 2026). On the PostgreSQL side, 18 is current and 14 through 17 are still supported.

When to Use PostgreSQL

  • You're building a product with complex data relationships, analytics queries, or reporting
  • Your schema has semi-structured data that benefits from JSONB indexing
  • You need geospatial capabilities (PostGIS is unmatched)
  • You're building an AI/ML application that needs vector similarity search (pgvector)
  • You're distributing proprietary software and can't afford GPL compliance overhead
  • Your team will be doing complex SQL — window functions, CTEs, lateral joins
  • You want a managed cloud offering: Supabase, Neon, AWS RDS, and most cloud databases now prefer PostgreSQL

When to Use MySQL

  • You're building on the LAMP stack or using CMS platforms like WordPress, Drupal, or Magento
  • Your team has deep MySQL DBA expertise and operational tooling already built around it
  • You need mature multi-primary active-active replication for globally distributed writes
  • Your workload is simple, read-heavy CRUD and the MySQL ecosystem tools (Percona Toolkit, pt-query-digest) are already in your workflow
  • You're migrating from or integrating with an existing MySQL-based system

When to Use Both (or Neither)

Some architectures legitimately use both. A platform might run MySQL for its user authentication and billing system (LAMP stack legacy) while running PostgreSQL for its analytics and search systems. Prisma, Drizzle, and other TypeScript ORMs support both, making this less operationally painful than it sounds.

When to use neither: If your data is genuinely document-oriented with highly variable schemas and you're doing few joins, MongoDB is a reasonable choice. If you need sub-millisecond key-value access, Redis. The mistake is choosing a document database because you don't want to define a schema upfront — that debt compounds quickly.

Architecture: Request Flows Compared

Rendering diagram...

Final Verdict

For new projects in 2026, PostgreSQL is the default choice. It has better JSON indexing ergonomics, a richer extension ecosystem, a stronger query planner for analytics, permissive licensing, and it's where the community is heading — 58.2% of professional developers reported using it in Stack Overflow's 2025 survey, up nearly seven points year over year.

MySQL is the right call when you're deep in the LAMP ecosystem, have existing MySQL expertise and tooling, or need mature multi-primary replication without additional infrastructure. It's not the wrong choice — it powers some of the world's largest web applications. But for a new project with no legacy constraints, PostgreSQL gives you more headroom.

The one caveat: if you skip PgBouncer and don't tune autovacuum, you'll have a bad time. PostgreSQL rewards teams that take operations seriously.

Comments (0)

No comments yet. Be the first to share your thoughts!

Related Articles

A monotonic stack maintains elements in order and pops when that order breaks — finding the next greater element for every popped value in O(n) total.
AdminAugust 3, 20265 min read
The EventEmitter pattern lets components in the same process react to the same event without being directly coupled — no message broker needed.
AdminAugust 3, 20266 min read
One file to guard every route — plus the Next.js 16 rename that moves it off the Edge runtime, the request-vs-response header trap that leaks user IDs to the browser, and the CVE that explains why this can never be your only auth layer.
AdminAugust 3, 20268 min read