PostgreSQL or MySQL: how we choose the database for a web app
Database choice turns into religion online, usually led by people defending a decision they made in 2015. In practice, a typical web app or SaaS runs well on either engine, and Laravel supports both without making you feel the difference. The honest way to decide is to ignore the general debate and look at the short list of differences that actually show up in the features you ship.
What you get by default with PostgreSQL
A few PostgreSQL features come up repeatedly in the apps we build, and each one removes work you would otherwise hand-roll in application code:
- JSONB with indexing. Product attributes, settings payloads, webhook bodies, provider responses. Store the shapeless data as
jsonb, index the paths you query with a GIN index, and the "flexible column" problem is solved at the database layer instead of by an EAV table nobody can reason about. - Partial and expression indexes.
CREATE INDEX ... WHERE deleted_at IS NULLkeeps the common query fast and the index small. Expression indexes overlower(email)remove the classic case-sensitivity bug class. - Exclusion constraints. Booking systems, reservations, shift scheduling: two overlapping time ranges for the same resource is a constraint the database can refuse, not a race your code has to catch (our earlier post on race conditions covers why application-level checks alone lose that fight).
- Transactional DDL. An
ALTER TABLEthat fails rolls back cleanly instead of leaving the schema half-migrated. In MySQL, DDL statements commit implicitly, which is one reason schema changes there demand double the care. - Richer CTE support. Recursion for org trees and bill-of-materials queries stays readable instead of becoming a stored procedure or an N+1 loop in the app.
None of these make MySQL unusable. They make certain features cheaper to build correctly, and those features (flexible payloads, scheduling constraints, tricky reporting) appear in a large share of client work.
Where MySQL still wins
MySQL (and its forks, chiefly MariaDB) is the right call in a few concrete situations. Your hosting or managed platform offers MySQL as the polished option and PostgreSQL as the second-class one. The existing production database is MySQL, and the cost of a migration outweighs the benefit of new features. Your operating team knows MySQL replication and tooling from years of running it, and that knowledge is worth more than a feature list. MySQL's replication remains easy to operate, and its ecosystem of hosting panels and tools is wide.
What does not count as a reason: folklore benchmarks. On real workloads, the difference is measured in the margins, and for reads-heavy web traffic both engines saturate long before the network and the application do. Also irrelevant: "we might need MySQL later". Switching engines later costs a full migration plus revisions to every query that leaned on vendor-specific SQL, so the cost runs in both directions.
The decision procedure
- Default to PostgreSQL for a new project. You get the widest set of tools for the price of zero decisions.
- Switch to MySQL only when a concrete, current reason appears: platform constraints, an existing estate, or deep team experience.
- Once chosen, lean into the engine's strengths deliberately rather than writing "portable" SQL that uses none of them. Portable SQL that avoids everything specific is portable in the way a bicycle with square wheels is universal.
Whichever you pick, the same habits decide real-world performance: measure queries, index what the actual queries use, and keep long migrations off the live tables. The engine is rarely where the slowness lives.