MySQL or Postgres for Laravel: What Actually Differs
Eloquent hides most of the distance between them, and then a handful of features do not travel. Here is which ones, and which of them you would notice a year in.
Eloquent is good enough at hiding the difference that most teams choose by habit and never find out what they chose. That is usually fine. It stops being fine at four specific points, and all four arrive long after the decision.
Where they are genuinely the same
Everything most applications do. select, insert, joins, transactions,
indexes, foreign keys, the query builder, the schema builder, migrations,
factories and every Eloquent relation type. If the application is CRUD over a
relational schema, you will not feel a difference for a long time.
So the honest framing is not "which is better". It is: which of the four divergences below will your application actually reach.
1. JSON, where Postgres is ahead and it matters
Both store JSON. Only one indexes arbitrary paths in it well.
$table->json('settings');On Postgres you want jsonb rather than json, and Laravel's jsonb() gives
you it. The difference is that jsonb is parsed and stored in a binary form
that a GIN index can cover, so a query like this uses an index:
Order::where('meta->channel', 'marketplace')->get();On MySQL a generated column plus an index on it gets you to the same place for one known path, which is fine when you have one known path and tedious when the shape is genuinely dynamic.
If the application stores a settings blob per tenant, a payload per webhook, or anything you will later want to filter on without knowing today which key - that is the strongest single argument for Postgres in a Laravel application.
2. Case sensitivity, which is a behavioural trap
MySQL's default collation is case-insensitive. Postgres is case-sensitive.
User::where('email', 'Test@example.com')->first();That finds test@example.com on MySQL and finds nothing on Postgres. Every
application written against MySQL has some number of comparisons that work
only because of that default, and nobody has written down which ones.
Neither behaviour is wrong. What is wrong is discovering the difference during a migration. Normalise on write - lowercase the email in a mutator, add a unique index to the normalised column - and the question stops mattering in either engine.
The same applies to ordering. ORDER BY name puts apple and Apple
differently in the two, which turns up in paginated lists as records that
appear on two pages or on none.
3. Constraints and types Postgres has and MySQL does not
Check constraints that actually check. A status column restricted to four values, enforced by the database rather than by a form request that a queued job can bypass.
Partial indexes. An index over only the rows that matter - WHERE deleted_at IS NULL on a soft-deleting table, or only the active subscriptions.
On a large table with a small hot subset this is a large win and MySQL has no
equivalent.
Real array and range types, and extensions. If you are doing anything geographic, PostGIS is the reason the decision is already made.
INSERT ... RETURNING, which Laravel exposes and which means an insert
that needs the generated row back is one round trip rather than two.
4. The operational differences, which are the ones that wake you up
Schema changes. Both can lock. Postgres adds a nullable column instantly
and builds an index CONCURRENTLY - which Laravel does not emit, so you write
it by hand, and it cannot run inside a transaction. Recent MySQL has instant
column addition for many cases and online DDL for many more. Neither is safe
by default on a large table, and
the failure mode is identical.
Replication and connections. Postgres connections are more expensive, which is why a Laravel application with a large worker fleet reaches a connection limit sooner and why a pooler appears in front of it earlier. MySQL tolerates a higher raw connection count. This interacts directly with how many queue workers you run.
Full-text search. Postgres has usable built-in full-text search with ranking. MySQL's is weaker. Both are a stopgap before a real search engine, but the stopgap lasts longer on Postgres.
How to actually choose
Choose Postgres if the data model has JSON you will query, or constraints you want enforced rather than trusted, or anything geographic, or reporting that will want window functions and CTEs before long.
Choose MySQL if your team and your hosting already run it and the application is straightforward relational work. Familiarity in the people who will be paged at three in the morning is a real engineering input, not a concession.
And whichever you choose, pin it everywhere. The single most expensive version of this decision is a team developing on SQLite, testing on MySQL and running Postgres in production, discovering the differences one incident at a time. Your local environment, your CI and your production database should be the same engine and the same major version - for reasons that show up in the test suite too.
