Key Takeaways

  • PostgreSQL overtook MySQL in developer adoption in 2022 and now leads 55.6% to 40.5% (Stack Overflow, 2025)
  • PostgreSQL's JSONB stores JSON in a binary format with GIN indexing; MySQL's JSON requires generated columns to index specific paths
  • MySQL CHECK constraints were silently ignored before version 8.0.16 (April 2019), a frequent source of legacy data issues
  • The three most common DDL surprises when porting between databases: auto-increment syntax, boolean type, and case sensitivity defaults
Rack-mounted servers in a data center, representing MySQL and PostgreSQL database infrastructure choices
Choosing between MySQL and PostgreSQL is as much about your team's existing stack as it is about features. Photo: Unsplash

At a Glance

PostgreSQL and MySQL share the same SQL fundamentals but diverge in schema-critical ways that matter at design time. PostgreSQL has led MySQL in developer adoption since 2022, reaching 55.6% vs 40.5% in the 2025 Stack Overflow Developer Survey (89,000+ respondents). That shift reflects real differences in capability, especially around JSON, type system depth, and standards compliance.

FeatureMySQLPostgreSQL
Auto-increment primary keysAUTO_INCREMENTSERIAL or GENERATED ALWAYS AS IDENTITY
BooleansTINYINT(1) (convention)Native BOOLEAN type
JSON supportJSON (validated, not directly indexed)JSON and JSONB (binary, GIN-indexable)
ArraysNot supported nativelyNative array columns
EnumsBuilt-in ENUM typeCustom types or CHECK constraints
Full-text searchFull-text indexes on InnoDBBuilt-in tsvector with GIN indexes
CHECK constraintsEnforced from 8.0.16+ (April 2019)Always enforced
Case sensitivityCase-insensitive by default (utf8mb4_unicode_ci)Case-sensitive by default

How Does Auto-Increment Work in MySQL vs PostgreSQL?

The auto-increment syntax is the first thing you'll notice when porting DDL between the two databases. MySQL uses AUTO_INCREMENT as a column attribute, added inline. PostgreSQL historically used SERIAL as a shorthand type. Since PostgreSQL 10 (released 2017), the preferred approach is GENERATED ALWAYS AS IDENTITY, which is the SQL-standard equivalent. Both do the same job. The syntax just doesn't transfer directly.

-- MySQL
CREATE TABLE users (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (id)
);

-- PostgreSQL (modern, preferred)
CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);

-- PostgreSQL (legacy shorthand, still works)
CREATE TABLE users (
    id SERIAL PRIMARY KEY
);

One practical difference: SERIAL creates a backing sequence object that's loosely coupled to the column. GENERATED ALWAYS AS IDENTITY ties the sequence directly to the column, which makes dumps and restores more predictable.

MySQL's AUTO_INCREMENT and PostgreSQL's GENERATED ALWAYS AS IDENTITY serve the same purpose — generating a unique integer primary key — but are not syntax-compatible. GENERATED ALWAYS AS IDENTITY, introduced in PostgreSQL 10 (released 2017), ties the sequence directly to the column, making schema dumps and restores more predictable than the older SERIAL shorthand.

Does MySQL Have a Native Boolean Type?

MySQL has no native boolean type. TINYINT(1) is a convention, not a constraint. It stores any small integer, so a value of 5 or -3 is perfectly legal at the database level. ORMs like Laravel and Rails treat TINYINT(1) as boolean automatically, which hides the issue in application code. But it means your schema doesn't actually enforce boolean semantics.

PostgreSQL's native BOOLEAN type accepts TRUE/FALSE, 't'/'f', 'yes'/'no', and 1/0. Anything else raises a type error. It's a small thing, but it catches bad inserts at the database layer instead of silently storing garbage.

If your ORM handles the mapping, you won't feel this difference day to day. Where it matters is raw SQL inserts and data migrations, where application-layer validation isn't running.

MySQL has no native boolean type; TINYINT(1) is a convention that stores any small integer, meaning values like 5 or -3 are valid at the database layer. PostgreSQL's native BOOLEAN type enforces true boolean semantics and rejects invalid input with a type error. ORMs abstract this difference, but raw SQL inserts and data migrations expose it directly.

Which Database Handles JSON Better: MySQL or PostgreSQL?

PostgreSQL's JSONB type stores JSON in a decomposed binary format. That means you can build a GIN index directly on the whole document and run fast key-existence queries without scanning every row. MySQL's JSON type validates the format on insert and supports path queries via JSON_EXTRACT(), but standard column indexes don't apply to JSON columns. The workaround is a generated column with an index on the specific path you query.

-- PostgreSQL: index the whole JSONB document
CREATE INDEX idx_meta ON events USING GIN (meta);

-- Query by key existence — uses the GIN index
SELECT * FROM events WHERE meta ? 'user_id';

-- MySQL: workaround — generated column + index on one path
ALTER TABLE events
    ADD COLUMN user_id_extracted VARCHAR(36)
    GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(meta, '$.user_id'))) STORED,
    ADD INDEX idx_user_id (user_id_extracted);

For occasional JSON reads, MySQL's approach works fine. For workloads where you're querying by document content, filtering large datasets by nested keys, or running aggregations across JSON fields, JSONB wins clearly.

PostgreSQL's JSONB type stores JSON in a decomposed binary format, enabling GIN indexes on the full document for fast key-existence and path queries. MySQL's JSON type validates input on insert but requires generated columns to index specific paths. For document-style queries, JSONB performs substantially better, according to Bytebase's technical analysis (bytebase.com, May 2025).

Are CHECK Constraints Enforced in MySQL?

MySQL parsed CHECK constraints in DDL from early versions but silently discarded them before version 8.0.16, released in April 2019 (MySQL official blog). Any schema written before 8.0.16 that relies on CHECK constraints for data integrity isn't actually enforcing them. This is a known source of legacy data quality issues in older MySQL codebases.

PostgreSQL has always enforced CHECK constraints fully. If you write CHECK (age >= 0), the database rejects any insert that violates it, regardless of version. No version gating, no silent failures.

-- This works identically in both databases on modern versions:
CREATE TABLE products (
    id INT PRIMARY KEY,
    price DECIMAL(10,2) CHECK (price >= 0),
    stock INT CHECK (stock >= 0)
);

-- But on MySQL < 8.0.16, those CHECK constraints are parsed and ignored.

If you're running MySQL 8.0.15 or older, don't assume CHECK constraints are enforced. Verify your MySQL version with SELECT VERSION(); before relying on them.

Is MySQL or PostgreSQL Case-Sensitive by Default?

MySQL's default collation (utf8mb4_unicode_ci) is case-insensitive, so WHERE name = 'Alice' matches 'alice', 'ALICE', and 'aLiCe'. PostgreSQL is case-sensitive by default. This trips up almost every MySQL-to-PostgreSQL migration.

Queries that returned the right results in MySQL silently return fewer rows in PostgreSQL. You'll need LOWER() for normalized comparisons, or ILIKE for case-insensitive pattern matching. It's not a major issue in greenfield projects, but it catches teams off guard in migrations.

-- MySQL: matches 'alice', 'Alice', 'ALICE'
SELECT * FROM users WHERE username = 'alice';

-- PostgreSQL: matches 'alice' only
SELECT * FROM users WHERE username = 'alice';

-- PostgreSQL: case-insensitive alternative
SELECT * FROM users WHERE LOWER(username) = 'alice';
-- or
SELECT * FROM users WHERE username ILIKE 'alice';

MySQL's default collation (utf8mb4_unicode_ci) is case-insensitive: WHERE username = 'alice' matches 'Alice', 'ALICE', and 'aLiCe'. PostgreSQL is case-sensitive by default, so the same query matches only the exact case. This collation difference causes the majority of query behavior bugs during MySQL-to-PostgreSQL migrations, requiring LOWER() or ILIKE in PostgreSQL for case-insensitive comparisons.

How Do MySQL and PostgreSQL Handle Foreign Key Constraints?

Both InnoDB (MySQL) and PostgreSQL enforce foreign keys at the database level. The practical difference is how you bypass them when you need to, such as during bulk imports or data migrations. MySQL uses SET FOREIGN_KEY_CHECKS = 0, which is quick but easy to forget to re-enable. PostgreSQL uses SET session_replication_role = replica, a more explicit step that makes "I'm disabling FK checks right now" deliberate rather than accidental.

Neither approach is obviously better. The MySQL flag is more familiar. The PostgreSQL approach forces you to be intentional. Whichever database you use, always verify FK checks are re-enabled after bulk operations.

Both MySQL (InnoDB) and PostgreSQL enforce foreign key constraints at the database level. They differ in how constraints are temporarily bypassed: MySQL uses SET FOREIGN_KEY_CHECKS = 0, while PostgreSQL uses SET session_replication_role = replica. The PostgreSQL approach requires a deliberate session-level change, making it harder to accidentally leave FK checks disabled after bulk import operations.

PostgreSQL crossed MySQL in developer adoption during the 2022 Stack Overflow Developer Survey and hasn't looked back. By 2025, 55.6% of all developers use PostgreSQL versus 40.5% for MySQL (Stack Overflow Developer Survey 2025, 89,000+ respondents). Among professional developers specifically, the gap widens: 58.2% for PostgreSQL versus 39.6% for MySQL.

60% 50% 40% 30% 2020 2021 2022 2023 2024 2025 55.6% 40.5% PostgreSQL MySQL Lines cross ~2022
Developer adoption 2020–2025. Source: Stack Overflow Developer Survey (annual). Data reflects "which databases have you used this year?"

The shift doesn't mean MySQL is dying. It still holds the #2 spot on DB-Engines by overall score (856.49 vs PostgreSQL's 682.68, May 2026) and remains dominant in legacy PHP/Laravel deployments and platforms like PlanetScale. But for new projects, the default assumption has shifted.

PostgreSQL MySQL All devs Professionals All devs Professionals 55.6% 58.2% 40.5% 39.6% Stack Overflow Developer Survey 2025 (89,000+ respondents)
PostgreSQL leads more strongly among professional developers (58.2% vs 39.6%) than in the all-developer population. Source: Stack Overflow Developer Survey 2025.

Which Should You Choose?

Two diverging paths through a forest, representing the choice between MySQL and PostgreSQL for a new project
The right choice depends more on your existing stack than on feature checkboxes. Photo: Unsplash

Here's a decision framework, stated plainly.

Choose PostgreSQL when…

  • Starting a new project with no existing database dependency
  • You need JSONB for document-style queries on nested data
  • Your schema uses arrays, ranges, or custom types
  • You need strict SQL compliance with CHECK constraints enforced without version gating
  • Your team has no strong database preference either way

Choose MySQL when…

  • Your team is already running MySQL and familiarity outweighs feature differences
  • You're using a MySQL-specific managed platform (PlanetScale, Vitess)
  • Your framework or ORM has MySQL-first defaults you'd have to work around
  • You need compatibility with a vendor product that only certifies MySQL

For most new web applications, PostgreSQL is the safer long-term choice. It's more standards-compliant, has a richer type system, and adoption trends favor it strongly. That said, switching databases mid-project has real costs. If your stack already speaks MySQL fluently, the marginal benefits of PostgreSQL rarely justify a migration.

PostgreSQL overtook MySQL in developer adoption during the 2022 Stack Overflow survey and now leads 55.6% to 40.5% overall, with a wider 18.6-point gap among professional developers alone (Stack Overflow Developer Survey 2025). For new projects, PostgreSQL is the stronger default, with better JSON handling, always-enforced constraints, and a more standards-compliant SQL dialect.

Whichever you choose, the schema design process is the same: model your entities and relationships first, pick appropriate data types, and use a free online database designer to validate the design visually before writing DDL.

Frequently Asked Questions

What is the main difference between MySQL and PostgreSQL?

MySQL is optimized for read-heavy web workloads and simpler to operate. PostgreSQL is more standards-compliant, with stronger support for complex queries, custom types, and JSON. PostgreSQL has led MySQL in developer adoption since 2022, reaching 55.6% vs 40.5% in the 2025 Stack Overflow Developer Survey.

Does MySQL or PostgreSQL handle JSON better?

PostgreSQL's JSONB type is more capable. It stores JSON in a binary format that supports GIN indexing on the full document, enabling fast key-existence and path queries. MySQL's JSON type is functional and supports path expressions, but you can't index a JSON column directly — only generated columns on specific paths.

Is AUTO_INCREMENT in MySQL the same as SERIAL in PostgreSQL?

They do the same job: auto-generate a unique integer primary key. But the syntax differs. MySQL uses AUTO_INCREMENT as a column attribute. PostgreSQL uses SERIAL as a shorthand type, or GENERATED ALWAYS AS IDENTITY in PostgreSQL 10+ (released 2017), which is the SQL-standard equivalent and the preferred form in new schemas.

Which should I choose for a new web application?

PostgreSQL is the safer long-term choice for new projects: more standards-compliant, better JSON and array support, and leading in developer adoption since 2022. Choose MySQL when joining a team already using it, or when using a MySQL-specific managed platform like PlanetScale where MySQL is the right operational fit.

Did PostgreSQL pass MySQL in developer popularity?

Yes. PostgreSQL overtook MySQL in the 2022 Stack Overflow Developer Survey and has widened the gap since. By 2025, PostgreSQL is used by 55.6% of developers vs 40.5% for MySQL. It's also the most admired database for the third consecutive year, with 66% of users wanting to continue using it (Stack Overflow, 2025).