Skip to content

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.

4 min read

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.

Related questions

Does Laravel favour one of them?
Not really. Both are first-class in the framework, both are covered by the query builder and the schema builder, and both are what Laravel is tested against. Where the framework leans is in defaults rather than support - and a default is not a recommendation for your workload.
Can we switch later?
The schema and the migrations move with modest effort. What does not move cheaply is anything that used a feature only one of them has, and anything where your code relied on a behavioural difference without naming it - sort order on mixed case being the classic. Budget for the data migration and the reporting queries, not for the ORM.
Which is faster?
For the queries a typical business application runs, the difference is far smaller than the difference a missing index makes. Both will serve you well past the point where the bottleneck is your schema. Choosing on benchmarks is optimising the wrong variable.
What about MariaDB?
It is close enough to MySQL for almost everything in Laravel and it has diverged in places - JSON handling and some newer syntax among them. Treat it as its own target rather than assuming parity, and pin which one your CI runs against.

← Back to all articles

Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page