PostgreSQL surpassed MySQL in 2025 to become the most widely used database among professional developers, according to the Stack Overflow Developer Survey. New projects now choose PostgreSQL over MySQL at a 3:1 ratio. I have run both in production at Commsult Indonesia — MySQL for legacy ERP modules inherited from older systems, PostgreSQL for all new NestJS services and APIs. After running both under real load, I have a clear opinion on when each earns its place.
The performance story is nuanced and depends heavily on workload type. PostgreSQL 17 improved parallel query performance by 35% and PostgreSQL 18's new asynchronous I/O subsystem improves sequential scan performance by 2–3x. PostgreSQL outperforms MySQL by roughly 13x in full table scans on large datasets. MySQL 8.4, however, consistently outperforms PostgreSQL by 20–30% on simple SELECT queries due to InnoDB's highly optimized buffer pool management and lower per-query overhead. For OLTP workloads dominated by simple key-value lookups, MySQL has a real edge.
PostgreSQL is the clear winner for complex analytical queries, JOINs across many tables, and window functions. Its query planner is more sophisticated — it considers more join strategies and can parallelize more operations. For ERP systems at Commsult Indonesia with complex reporting queries joining 8–12 tables, PostgreSQL execution plans are consistently faster and more predictable than equivalent MySQL queries. PostgreSQL also supports lateral joins, materialized views with concurrent refresh, and partial indexes — features that MySQL either lacks or implements partially.
PostgreSQL's JSONB type with GIN indexes is one of its killer features — it gives you relational and document database capabilities in a single engine. You can store semi-structured data (API responses, config blobs, event logs) as JSONB and query it with full indexing support and SQL functions. MySQL's JSON type has improved but still lacks GIN indexes and many of PostgreSQL's JSON path operators. For NestJS APIs that need to store flexible metadata alongside relational data, PostgreSQL's JSONB eliminates the need for a separate MongoDB instance.
┌──────────────────────────────────────────────────────┐
│ POSTGRESQL vs MYSQL FEATURE MATRIX │
├──────────────────────┬───────────────────────────────┤
│ PostgreSQL │ MySQL │
├──────────────────────┼───────────────────────────────┤
│ JSONB + GIN index │ JSON (no GIN index) │
│ Full table scan 13x │ Simple SELECT 20-30% faster │
│ Window functions │ Limited window functions │
│ Partial indexes │ No partial indexes │
│ BSD license │ GPL + commercial dual license │
│ PostGIS extension │ Spatial (limited) │
│ CTE MATERIALIZED │ CTE (less control) │
└──────────────────────┴───────────────────────────────┘From my experience running PostgreSQL on DigitalOcean Managed Databases for Commsult Indonesia, the pg_stat_statements extension is invaluable for identifying slow queries. Enable it on day one, not after you're already in a performance crisis. Run SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20 weekly and you will catch N+1 query problems and missing indexes before they become incidents.
MySQL's dual license (GPL + commercial) has historically pushed enterprises toward PostgreSQL, which uses a permissive BSD-style license with no strings attached. This is one reason cloud providers consistently offer better managed PostgreSQL services than MySQL — AWS RDS Aurora PostgreSQL, DigitalOcean Managed PostgreSQL, and Cloud SQL for PostgreSQL all have more features than their MySQL equivalents. For organizations that need to embed the database in commercial software or need freedom from licensing restrictions, PostgreSQL is the obvious choice.
Migrating between PostgreSQL and MySQL is non-trivial. SQL dialects differ in ways that bite you: PostgreSQL uses SERIAL or BIGSERIAL for auto-increment (now preferably GENERATED ALWAYS AS IDENTITY), MySQL uses AUTO_INCREMENT. String comparison is case-insensitive in MySQL by default, case-sensitive in PostgreSQL. Boolean handling differs. If you are starting a new project, choosing PostgreSQL avoids a future migration. If you have an existing MySQL codebase, the migration effort needs careful planning — I recommend pgloader for automated MySQL-to-PostgreSQL migrations.
-- PostgreSQL: JSONB with GIN index
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_events_payload ON events USING GIN (payload);
-- Query with full index support
SELECT * FROM events
WHERE payload @> '{"status": "completed"}'
AND payload->>'user_id' = '12345';
-- pg_stat_statements: find slow queries
SELECT query, calls, total_exec_time / calls AS avg_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;Both databases support streaming replication for read replicas and hot standby. PostgreSQL's streaming replication is synchronous or asynchronous and integrates with tools like Patroni for automatic failover. MySQL uses Group Replication or InnoDB Cluster for HA. In practice, the managed database offerings from cloud providers abstract most of this complexity — DigitalOcean Managed PostgreSQL includes read replicas, connection pooling via PgBouncer, and automated failover out of the box.
I hit a subtle bug when migrating a legacy ERP module from MySQL to PostgreSQL: MySQL's default transaction isolation is REPEATABLE READ, PostgreSQL's is READ COMMITTED. Code that relied on MySQL's phantom read prevention worked differently under PostgreSQL. We had a reporting query that returned inconsistent results under concurrent writes. Always audit your application's transaction assumptions when switching databases — this class of bug is hard to catch in testing but causes data consistency issues in production.
For any new project in 2025, PostgreSQL is the default choice. The ecosystem (ORMs, client libraries, extensions), the feature set, the licensing, and the performance on complex workloads all favor PostgreSQL. MySQL remains a reasonable choice if you have existing expertise, an existing MySQL stack, or a workload dominated by simple high-throughput SELECT queries where MySQL's InnoDB has the edge. At Commsult Indonesia, all new services use PostgreSQL via Prisma ORM, and we are gradually migrating legacy modules off MySQL as we touch them for other reasons.
Choose PostgreSQL when: you need complex queries with JOINs and aggregations, you want JSONB for flexible data, you need PostGIS for geospatial data, compliance requires BSD-licensed software, or you are starting a new project with no legacy constraints. Choose MySQL when: you have existing MySQL infrastructure and team expertise, your workload is dominated by simple high-throughput SELECTs, you need compatibility with specific tools that only support MySQL (some legacy CMSes), or you need the specific features of MySQL Group Replication.