When I started my first production project β a small SaaS tool for managing client invoices β I chose MySQL. Not because I had evaluated the options. Because every tutorial I had followed up to that point used MySQL, and LAMP stack examples were everywhere. It worked fine for about eight months. Then I needed full-text search, then I needed to store some flexible JSON config data per client, then I needed a specific type of window function for generating reports. Each time, I found that MySQL's implementation was limited or non-standard compared to what the documentation implied.
I migrated to PostgreSQL. The migration itself took a weekend. What took longer was understanding why I should have chosen Postgres from the beginning β and what the real differences are beyond the surface-level "both are SQL databases."
This is that explanation.
They're More Similar Than Different β But the Differences Matter
PostgreSQL and MySQL are both open-source relational databases. Both speak SQL. Both support transactions, indexes, foreign keys, and all the fundamentals. For a basic CRUD application β create, read, update, delete β you could use either and never notice a difference.
The differences show up when you need more. And modern applications almost always need more.
Standards Compliance: PostgreSQL Wins Clearly
PostgreSQL is one of the most SQL-standards-compliant databases in existence. When you write a query following the SQL standard, it almost always works in PostgreSQL as written. This matters more than it sounds β it means Stack Overflow answers, documentation examples, and queries you write for one PostgreSQL environment will work in another.
MySQL has historically deviated from the SQL standard in various ways. It used to silently truncate data that didn't fit a column instead of throwing an error. It used to treat empty strings as zero in numeric contexts. Many of these quirks have been fixed in recent MySQL versions (especially with strict mode enabled), but the history of non-standard behavior means you encounter more surprises.
When I was debugging a report query that gave me different results in development and production, the root cause was MySQL interpreting a GROUP BY clause differently than I expected. PostgreSQL would have thrown an error immediately.
JSON Support: PostgreSQL Is Years Ahead
Both databases support JSON columns. The difference is what you can do with that JSON.
PostgreSQL introduced JSONB β a binary JSON type β that stores JSON data in a decomposed format optimized for querying. You can index inside JSONB documents, run complex queries against nested fields, and use GIN indexes for fast array and key searches. The performance is close to a native relational column.
MySQL's JSON support, while improved in recent versions, lacks the indexing depth and query flexibility of JSONB. You can index one specific path at a time with generated columns, but it's far more cumbersome than PostgreSQL's approach.
In my invoicing project, I needed to store flexible configuration objects per client β different clients had different billing rules. In PostgreSQL, a config JSONB column handled this naturally. I queried specific keys in WHERE clauses, indexed the ones I filtered on most, and it was fast. Doing the equivalent in MySQL required significantly more work.
Full-Text Search: PostgreSQL Has It Built In
MySQL has full-text search, but it only works with MyISAM tables (the older engine) or with InnoDB for specific index types. The functionality is limited and requires explicit FULLTEXT index creation.
PostgreSQL's full-text search using tsvector and tsquery is a serious, feature-rich implementation. It supports multiple languages, custom dictionaries, ranking by relevance, phrase searching, and GIN indexing for fast results across large datasets. For many applications, PostgreSQL's built-in full-text search eliminates the need to run a separate Elasticsearch instance.
This was the feature that first made me seriously consider migrating. When my invoicing app needed a search bar that searched across client names, invoice descriptions, and line items β MySQL's options required either a separate search engine or slow LIKE queries. PostgreSQL solved it in one afternoon.
Concurrency: PostgreSQL's MVCC Is Superior
Both databases implement MVCC (Multi-Version Concurrency Control), but PostgreSQL's implementation is significantly better. In PostgreSQL, readers never block writers and writers never block readers β each transaction sees a consistent snapshot of the data as of when it started.
MySQL's InnoDB engine also uses MVCC, but with important limitations: certain operations still take table-level locks, and under high write concurrency, PostgreSQL consistently outperforms MySQL in benchmarks. For applications with many simultaneous users writing and reading β like a booking system or a real-time dashboard β PostgreSQL's concurrency model is more robust.
Advanced Data Types: PostgreSQL Has No Equal
PostgreSQL supports data types that simply don't exist in MySQL:
Arrays β a column can store an array of values. Instead of a join table for tags, store tags TEXT[] and query with WHERE 'invoice' = ANY(tags).
Range types β store a range of dates, times, or numbers natively. Perfect for booking systems where you need to detect overlapping reservations.
UUID β a native type, not just a string. Works correctly in indexes and comparisons.
ENUM β both databases support this, but PostgreSQL handles it more cleanly.
Custom types β PostgreSQL lets you define your own composite types, which is powerful for domain modeling.
When I discovered PostgreSQL's range types while rebuilding a scheduling feature that previously used a nightmare of start/end timestamp columns and application-level overlap checks β that was the moment I fully committed to PostgreSQL for all future projects.
When MySQL Is Still a Reasonable Choice
MySQL is not bad. It is a mature, stable, widely-deployed database that powers enormous applications including Wikipedia and Twitter (historically). It has a massive community, excellent hosting support everywhere, and a straightforward setup experience.
Choose MySQL when: you're joining an existing team or project already using it, you need maximum compatibility with a specific legacy PHP application or CMS (WordPress uses MySQL), or your hosting environment only supports MySQL.
Choose PostgreSQL when: you're starting a new project and have the freedom to choose, you need any of the advanced features described above, or you want the database that will grow with you without hitting walls.
The Honest Recommendation
If you're starting fresh today with no external constraints, choose PostgreSQL. The learning curve is nearly identical to MySQL, the hosting support is universal (every major cloud provider has managed PostgreSQL), and you'll never outgrow it. MySQL is a safe choice if you're already in its ecosystem; PostgreSQL is the better choice if you're not.
I've started every project with PostgreSQL since that migration weekend. I haven't once wished I had chosen MySQL instead. The same cannot be said for the year I spent on MySQL before I switched.
