PostgreSQL vs MySQL: Which Database Fits Your Requirements

A sourced comparison of PostgreSQL 18 and MySQL 8.4 LTS, with a Decision Lab that explains which one your requirements point to, and why.

1. How to choose

PostgreSQL and MySQL are both open-source relational databases . The differences that should decide between them are specific: how the licence fits the way you ship software, which data types and queries you need, what each one guarantees about concurrent transactions, and how much of high availability is built in.

This guide compares PostgreSQL 18 with the MySQL 8.4 LTS manual . MySQL 9.7 is a newer LTS series, and releases from 26.7 use calendar version numbers ; differences in those newer series are not covered here.

The Decision Lab

Pick the requirements that matter to you. Each answer turns on rules written from the evidence in this guide, and every reason links to the claim it rests on. The lab never scores the databases: it lists the reasons on each side and only says one option leads when the reasons separate them.

The lab deliberately leaves out speed, cost and your team's experience. No benchmark we found states its method, hardware, versions and conditions well enough to rely on, and the other two depend on you.

2. At a glance

The table summarises the documented differences, one cited statement per cell. The chapters that follow explain each group.

3. Licence, editions and releases

Licence

PostgreSQL is released under the PostgreSQL License, which the project describes as a liberal open-source licence similar to BSD or MIT .

MySQL is developed, distributed and supported by Oracle . Oracle dual-licenses the server and client libraries under GPL version 2 and under a commercial licence ; the manual names embedding MySQL code in a commercial application as a reason to choose the commercial version .

If you distribute software that includes MySQL, check the GPL's obligations or budget for a commercial licence. Running MySQL behind your own service is the common case the Decision Lab treats as neutral.

Editions

PostgreSQL has one edition; optional contrib modules ship with the distribution but are not part of the core system . MySQL Community Edition is the free, GPL-licensed download , and the commercial Enterprise Edition adds components such as Enterprise Backup, the thread pool, Transparent Data Encryption, Firewall and Audit, with 24x7 support .

Release models

PostgreSQL releases a new major version about once a year and supports each for 5 years . Version 18 was first released on September 25, 2025 and is supported until November 14, 2030 .

MySQL has two tracks, LTS and Innovation, both described by Oracle as production-grade . An LTS series gets 5 years of premier and 3 years of extended support ; an Innovation release is supported only until the next one . Within an LTS series there are no feature removals , upgrades go from one LTS series to the next without skipping one , and point releases within a series can be upgraded in place .

On October 26, 2026 the MySQL download page offered 26.10.0, 26.7.0, 9.7.4 LTS, 8.4.13 LTS and 8.0.46 . 9.7 was the last release with sequential version numbers; 26.7.0 (July 2026) was the first with calendar versioning, which by itself does not say whether a release is LTS or Innovation .

End-of-support dates for MySQL 8.4 and 9.7 were not found on the pages used, only the policy , and whether 26.7 and 26.10 are Innovation releases is inferred from the download page alone .

4. Architecture

Processes or threads

PostgreSQL gives each client connection its own backend process ; max_connections is typically 100 by default . MySQL runs as one server process with a dedicated thread per connection, reused from a thread cache when possible , and max_connections defaults to 151 .

Both approaches cost resources per connection. The MySQL manual says the default one-thread-per-connection model degrades as more clients connect , and its alternative, the thread pool, ships only in Enterprise Edition .

How tables are stored

PostgreSQL stores each table as an array of fixed-size pages, 8 kB by default , and an UPDATE writes a new version of the row rather than overwriting it .

InnoDB, MySQL's default and recommended storage engine , stores rows in a clustered index that is typically the primary key . Without a primary key it uses the first suitable unique index, or a hidden 6-byte row ID . Each secondary-index entry stores the row's primary key, so a long primary key makes every secondary index larger .

Memory and crash safety

PostgreSQL's shared_buffers defaults to typically 128 MB ; InnoDB's buffer pool defaults to 128 MB , and on dedicated servers up to 80% of memory is often given to it .

PostgreSQL writes data changes only after the write-ahead log records describing them reach permanent storage . InnoDB uses a redo log for crash recovery , 100 MiB by default , and first writes pages to a doublewrite buffer so a page whose write was interrupted can be recovered . MySQL 8 keeps object metadata in a transactional data dictionary .

Extending the server

PostgreSQL extensions package new data types, functions, operators and index methods for installation with one CREATE EXTENSION . MySQL's extension point is the storage engine: besides InnoDB it lists MyISAM, MEMORY, CSV, ARCHIVE, BLACKHOLE, NDB, MERGE and FEDERATED .

5. Concurrency and transactions

Both databases use multiversion concurrency control, but they keep old row versions in different places.

  • PostgreSQL leaves old versions in the table, and periodic vacuuming, usually by the autovacuum daemon, removes them .
  • InnoDB keeps old versions in undo logs in rollback segments , and purge removes deleted rows once their undo records are discarded .

Default isolation

PostgreSQL defaults to Read Committed . Of the four standard levels it implements three, with Read Uncommitted behaving like Read Committed . InnoDB offers all four SQL:1992 levels and defaults to REPEATABLE READ , where every consistent read in a transaction uses the snapshot of its first read and searches take next-key locks that prevent phantom rows .

So the same code can see different data by default: two SELECTs in one PostgreSQL transaction can return different results if another transaction commits in between , while in InnoDB both read the same snapshot .

Serializable

PostgreSQL implements Serializable with Serializable Snapshot Isolation, whose predicate locks never block and cannot deadlock ; transactions that cannot be serialized fail and must be retried . In InnoDB, SERIALIZABLE behaves like REPEATABLE READ, except that plain SELECTs become SELECT … FOR SHARE when autocommit is off .

Durability defaults

Both acknowledge a commit only after it is safe on disk by default: PostgreSQL waits for the WAL flush with synchronous_commit on , and InnoDB flushes its logs at every commit with innodb_flush_log_at_trx_commit set to 1 . Setting it to 0 or 2 flushes about once a second and can lose recent transactions in a crash .

Schema changes inside transactions

In MySQL, atomic DDL makes one DDL statement all-or-nothing , but every DDL statement implicitly commits the active transaction, so DDL cannot be rolled back together with other statements .

The current PostgreSQL documentation does not state a general rule for DDL inside transactions . It does say that TRUNCATE is rolled back if its transaction does not commit , that a regular CREATE INDEX can run inside a transaction block while CREATE INDEX CONCURRENTLY cannot , and that CREATE DATABASE cannot . A community wiki page from 2012 states that PostgreSQL has transactional DDL .

Long transactions

Both engines pay for transactions that stay open. In PostgreSQL an open transaction keeps VACUUM from removing versions it might still see, so tables can bloat . In InnoDB, transactions that are not committed keep undo data from being discarded, which can fill the undo tablespace , and purge can fall behind when rows are inserted and deleted at similar rates .

6. SQL and data types

JSON

PostgreSQL has two JSON types: json keeps the input text, while jsonb stores a binary form that is faster to process and can be indexed . The documentation recommends jsonb for most applications , indexes it with GIN , and supports the SQL/JSON path language and JSON_TABLE .

MySQL has a native JSON type that validates documents and stores them in a binary format , can update part of a document in place , and has JSON_TABLE . A JSON column cannot be indexed directly: you index a generated column that extracts a scalar .

Full-text search and geospatial data

Both have built-in full-text search. PostgreSQL uses tsvector and tsquery, indexed with GIN or GiST ; MySQL has FULLTEXT indexes on InnoDB text columns and an ngram parser for Chinese, Japanese and Korean .

For geospatial data MySQL has built-in OGC spatial types with SPATIAL indexes . PostgreSQL gets it from PostGIS, a separate project installed as an extension that adds spatial storage, indexing, functions and raster support and is GPL-licensed .

Types beyond the standard

PostgreSQL columns can be multidimensional arrays . Range and multirange types represent spans of values, and exclusion constraints can, for example, forbid overlapping ranges . Composite and enum types are built in, and CREATE TYPE defines new composite, enum, range and base types . Generated columns are virtual by default .

Constraints and statements

Both enforce CHECK constraints. PostgreSQL's cannot reference data outside the row being checked ; MySQL enforces them from 8.0.16, and each can be ENFORCED or NOT ENFORCED . MySQL 8 has common table expressions, including recursive ones, and window functions . PostgreSQL has a standard MERGE statement , declarative range, list and hash partitioning , row-level security policies that deny by default once enabled , and parallel query .

Defaults that change behaviour

MySQL's default character set is utf8mb4 with collation utf8mb4_0900_ai_ci , and strict SQL mode is on by default, which decides how invalid or missing values are handled on INSERT and UPDATE .

7. Replication and high availability

Both replicate asynchronously by default, so a replica can lag and a crash of the primary can lose transactions that had not yet reached it .

Replication — Streaming WAL to standbys ; logical publish/subscribe — Binary-log events, row-based by default ; GTIDs; Read queries on replicas — Hot standby, read-only, on by default — Replicas receive the source's changes ; in Group Replication's single-primary mode the other members are read-only; Waiting for replicas — Synchronous replication, priority or quorum — Semisynchronous; falls back to asynchronous on timeout; Automatic failover — Not built in — Group Replication and InnoDB Cluster; Several writable servers — No synchronous multimaster — Group Replication multi-primary

PostgreSQL

Streaming replication sends WAL records to standbys as they are generated . Synchronous replication is off until synchronous_standby_names is set , and can wait for several standbys by priority or quorum . Standbys can relay WAL onwards, asynchronously only . Logical replication works from a replication identity, usually the primary key, and can cross platforms, but does not replicate schema, DDL or sequences .

PostgreSQL does not provide the software that detects a failed primary; the docs say many such tools exist . Promotion is triggered with pg_ctl promote or pg_promote() , and the old primary must be fenced so two primaries never run .

MySQL

The binary log, on by default , drives replication and point-in-time recovery . Semisynchronous replication is implemented as plugins ; for truly synchronous replication the manual points to NDB Cluster .

Group Replication provides a built-in membership service and runs single-primary by default, electing a new primary automatically, or multi-primary . It requires InnoDB and checks transactions for conflicts at commit, rolling back the losers , and every replicated table needs a primary key or equivalent . InnoDB Cluster combines at least three servers running Group Replication with MySQL Shell's AdminAPI and MySQL Router .

8. Operations and defaults

Cleaning up old row versions

PostgreSQL relies on vacuuming, usually left to autovacuum ; InnoDB relies on purge threads , 1 by default on machines with 16 or fewer logical processors and 4 otherwise . Either falls behind when long transactions hold old versions .

Extensions and packaging

Installing a PostgreSQL extension usually needs superuser rights unless the extension is marked trusted . Packaged builds ship the contrib modules as a separate subpackage such as postgresql-contrib . pg_dump records an extension as one CREATE EXTENSION, so its files must be present when restoring , and extensions are upgraded with ALTER EXTENSION UPDATE .

Server defaults worth knowing

max_connections — typically 100 — 151; Main cache — shared_buffers typically 128 MB — buffer pool 128 MB; Autocommit — each statement is its own transaction without BEGIN — autocommit ON; Parallel workers per query — 2 — —; innodb_dedicated_server — — — OFF

Technologies

  • PostgreSQL: Compared in this guide (version 18).
  • MySQL: Compared in this guide (8.4 LTS manual).

Sources

  1. 5.6. System Columns — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  2. 13.2. Transaction Isolation — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  3. 13.5. Serialization Failure Handling — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  4. 19.11. Client Connection Defaults — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  5. MySQL 8.4 Reference Manual — MySQL 8.4 Reference Manual (Oracle), undated (8.4 manual, through 8.4.13)
  6. Commercial License for OEMs, ISVs and VARs — mysql.com (Oracle), undated
  7. MySQL Enterprise Edition — mysql.com (Oracle), undated
  8. 7.6.3 MySQL Enterprise Thread Pool — MySQL 8.4 Reference Manual, undated (8.4 manual)
  9. MySQL Enterprise Audit — MySQL 8.4 Reference Manual, undated (8.4 manual)
  10. MySQL Enterprise Firewall — MySQL 8.4 Reference Manual, undated (8.4 manual)
  11. 7.1.12.1 Connection Interfaces — MySQL 8.4 Reference Manual, undated (8.4 manual)
  12. Server System Variables — MySQL 8.4 Reference Manual, undated (8.4 manual)
  13. B.3.2.5 Too many connections — MySQL 8.4 Reference Manual, undated (8.4 manual)
  14. Introduction to InnoDB — MySQL 8.4 Reference Manual, undated (8.4 manual)
  15. Clustered and Secondary Indexes — MySQL 8.4 Reference Manual, undated (8.4 manual)
  16. 18 Alternative Storage Engines — MySQL 8.4 Reference Manual, undated (8.4 manual)
  17. MySQL Data Dictionary — MySQL 8.4 Reference Manual, undated (8.4 manual)
  18. 17.3 InnoDB Multi-Versioning — MySQL 8.4 Reference Manual, undated (8.4 manual)
  19. 17.7.2.3 Consistent Nonlocking Reads — MySQL 8.4 Reference Manual, undated (8.4 manual)
  20. 17.7.2.1 Transaction Isolation Levels — MySQL 8.4 Reference Manual, undated (8.4 manual)
  21. 17.7.1 InnoDB Locking — MySQL 8.4 Reference Manual, undated (8.4 manual)
  22. 17.7.2.2 autocommit, Commit, and Rollback — MySQL 8.4 Reference Manual, undated (8.4 manual)
  23. InnoDB Startup Options and System Variables — MySQL 8.4 Reference Manual, undated (8.4 manual)
  24. Redo Log — MySQL 8.4 Reference Manual, undated (8.4 manual)
  25. 17.6.4 Doublewrite Buffer — MySQL 8.4 Reference Manual, undated (8.4 manual)
  26. 1.3 MySQL Releases: Innovation and LTS — MySQL 8.4 Reference Manual, undated (8.4 manual)
  27. 17.5.1 Buffer Pool — MySQL 8.4 Reference Manual, undated (8.4 manual)
  28. 19 Replication — MySQL 8.4 Reference Manual, undated (8.4 manual)
  29. 19.4.10 Semisynchronous Replication — MySQL 8.4 Reference Manual, undated (8.4 manual)
  30. 19.1.3.1 GTID Format and Storage — MySQL 8.4 Reference Manual, undated (8.4 manual)
  31. 19.2.1 Replication Formats — MySQL 8.4 Reference Manual, undated (8.4 manual)
  32. 7.4.4 The Binary Log — MySQL 8.4 Reference Manual, undated (8.4 manual)
  33. 20 Group Replication — MySQL 8.4 Reference Manual, undated (8.4 manual)
  34. 20.1.3.1 Single-Primary Mode — MySQL 8.4 Reference Manual, undated (8.4 manual)
  35. 20.1.3 Multi-Primary and Single-Primary Modes — MySQL 8.4 Reference Manual, undated (8.4 manual)
  36. Group Replication Requirements — MySQL 8.4 Reference Manual, undated (8.4 manual)
  37. 1.3 MySQL Releases: Innovation and LTS — MySQL 26.10 Reference Manual, undated (26.10 manual)
  38. 23 InnoDB Cluster — MySQL 8.4 Reference Manual, undated (8.4 manual)
  39. 13.5 The JSON Data Type — MySQL 8.4 Reference Manual, undated (8.4 manual)
  40. 15.1.20.6 CHECK Constraints — MySQL 8.4 Reference Manual, undated (8.4 manual)
  41. CHECK Constraints — MySQL 8.0 Reference Manual, undated (8.0 manual)
  42. WITH (Common Table Expressions) — MySQL 8.4 Reference Manual, undated (8.4 manual)
  43. 14.20 Window Functions — MySQL 8.4 Reference Manual, undated (8.4 manual)
  44. What Is New in MySQL 8.0 — MySQL 8.0 Reference Manual, undated (8.0 manual)
  45. 15.1.1 Atomic Data Definition Statement Support — MySQL 8.4 Reference Manual, undated (8.4 manual)
  46. Statements That Cause an Implicit Commit — MySQL 8.4 Reference Manual, undated (8.4 manual)
  47. 14.9 Full-Text Search Functions — MySQL 8.4 Reference Manual, undated (8.4 manual)
  48. MySQL 26.10 Reference Manual — MySQL 26.10 Reference Manual (Oracle), undated (26.10.0 manual)
  49. 13.4 Spatial Data Types — MySQL 8.4 Reference Manual, undated (8.4 manual)
  50. Server SQL Modes — MySQL 8.4 Reference Manual, undated (8.4 manual)
  51. 12.3.2 Server Character Set and Collation — MySQL 8.4 Reference Manual, undated (8.4 manual)
  52. Download MySQL Community Server — dev.mysql.com (Oracle), undated
  53. 1.2.1 What is MySQL? — MySQL 8.4 Reference Manual, undated (8.4 manual)
  54. MySQL Community Edition — mysql.com (Oracle), undated
  55. 36.17. Packaging Related Objects into an Extension — PostgreSQL 18 Documentation, undated (18 docs)
  56. 8.16. Composite Types — PostgreSQL 18 Documentation, undated (18 docs)
  57. 8.7. Enumerated Types — PostgreSQL 18 Documentation, undated (18 docs)
  58. CREATE TYPE — PostgreSQL 18 Documentation, undated (18 docs)
  59. CREATE DATABASE — PostgreSQL 18 Documentation, undated (18 docs)
  60. CREATE INDEX — PostgreSQL 18 Documentation, undated (18 docs)
  61. TRUNCATE — PostgreSQL 18 Documentation, undated (18 docs)
  62. 13.6. Caveats — PostgreSQL 18 Documentation, undated (18 docs)
  63. Transactional DDL in PostgreSQL: A Competitive Analysis — PostgreSQL wiki, 2012-06-11 (last edited)
  64. 26.2. Log-Shipping Standby Servers — PostgreSQL 18 Documentation, undated (18 docs)
  65. CREATE EXTENSION — PostgreSQL 18 Documentation, undated (18 docs)
  66. 26.4. Hot Standby — PostgreSQL 18 Documentation, undated (18 docs)
  67. 26.3. Failover — PostgreSQL 18 Documentation, undated (18 docs)
  68. 26.1. Comparison of Different Solutions — PostgreSQL 18 Documentation, undated (18 docs)
  69. Chapter 29. Logical Replication — PostgreSQL 18 Documentation, undated (18 docs)
  70. 29.8. Restrictions (Logical Replication) — PostgreSQL 18 Documentation, undated (18 docs)
  71. 47.2. Logical Decoding Concepts — PostgreSQL 18 Documentation, undated (18 docs)
  72. 19.6. Replication (server configuration) — PostgreSQL 18 Documentation, undated (18 docs)
  73. 5.12. Table Partitioning — PostgreSQL 18 Documentation, undated (18 docs)
  74. 5.9. Row Security Policies — PostgreSQL 18 Documentation, undated (18 docs)
  75. 5.5. Constraints — PostgreSQL 18 Documentation, undated (18 docs)
  76. Appendix F. Additional Supplied Modules and Extensions — PostgreSQL 18 Documentation, undated (18 docs)
  77. MERGE — PostgreSQL 18 Documentation, undated (18 docs)
  78. Chapter 15. Parallel Query — PostgreSQL 18 Documentation, undated (18 docs)
  79. 19.4. Resource Consumption — PostgreSQL 18 Documentation, undated (18 docs)
  80. F.38. postgres_fdw — PostgreSQL 18 Documentation, undated (18 docs)
  81. 5.4. Generated Columns — PostgreSQL 18 Documentation, undated (18 docs)
  82. PostGIS (home) — PostGIS PSC and OSGeo, undated
  83. Getting Started — PostGIS PSC and OSGeo, undated
  84. FAQ — PostGIS PSC and OSGeo, undated
  85. Versioning Policy — PostgreSQL Global Development Group, undated
  86. License — PostgreSQL Global Development Group, undated
  87. 8.14. JSON Types — PostgreSQL 18 Documentation, undated (18 docs)
  88. 51.2. How Connections Are Established — PostgreSQL 18 Documentation, undated (18 docs)
  89. 19.3. Connections and Authentication — PostgreSQL 18 Documentation, undated (18 docs)
  90. 13.2. Transaction Isolation — PostgreSQL 18 Documentation, undated (18 docs)
  91. 24.1. Routine Vacuuming — PostgreSQL 18 Documentation, undated (18 docs)
  92. 3.4. Transactions — PostgreSQL 18 Documentation, undated (18 docs)
  93. 9.16. JSON Functions and Operators — PostgreSQL 18 Documentation, undated (18 docs)
  94. 12.1. Introduction (Full Text Search) — PostgreSQL 18 Documentation, undated (18 docs)
  95. 12.9. Preferred Index Types for Text Search — PostgreSQL 18 Documentation, undated (18 docs)
  96. 8.15. Arrays — PostgreSQL 18 Documentation, undated (18 docs)
  97. 8.17. Range Types — PostgreSQL 18 Documentation, undated (18 docs)
  98. 66.6. Database Page Layout — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  99. 28.3. Write-Ahead Logging (WAL) — PostgreSQL documentation, undated (current docs, PostgreSQL 18)
  100. 28.4. Asynchronous Commit — PostgreSQL documentation, undated (current docs, PostgreSQL 18)