MySQL vs Postgres vs SQLite

Use Postgres by default — it has the richest feature set and the best extension ecosystem. Use SQLite when the database can live next to the application, which is more often than most developers assume. Use MySQL when you have operational experience with it or a platform that expects it. All three are excellent; none of them is wrong.

Editorial judgement last reviewed

Which database should I use: SQLite, Postgres or MySQL?

This page deliberately does not rank these. They are three good engines with different deployment models, and a scoreboard would be dishonest.

Postgres is the default answer, and it should be. It has the strictest standards compliance of the three, the best type system (real arrays, JSONB, ranges, native UUIDs, custom types), and an extension ecosystem that no other database matches — PostGIS for geospatial, pgvector for embeddings, TimescaleDB for time series, pg_cron for scheduling. Its MVCC implementation gives you proper snapshot isolation. If you have no reason to choose otherwise, choose Postgres and stop reading.

SQLite is the most underrated option in web development. It is not a toy: it is the most widely deployed database engine in the world, it is exhaustively tested, and on modern hardware with WAL mode it handles far more concurrent read traffic than people expect. Because it runs in-process, queries cost microseconds rather than a network round trip, which means an application that would need careful query batching against Postgres can be naive against SQLite and still be faster. The constraint is a single writer at a time and the fact that your database lives on one machine's disk — a genuine constraint, and one that a large fraction of applications never actually hit.

MySQL is the pragmatic incumbent. It is fast, extremely well understood, replication is mature and battle-tested at enormous scale, and the managed hosting market is deep and cheap. Its type handling has historically been laxer than Postgres, and its extension story is thinner. If you already run it well, there is no urgent reason to move.

The wrong question is which is best. The right question is where your data should live: in the process, on a server you manage, or in a managed service.

Which one should you pick?

Choose Postgres if…

  • You want the default. It is the safest choice for a new project and the most transferable skill.
  • You need extensions: PostGIS, pgvector, full-text search, time-series, scheduled jobs inside the database.
  • Your data model wants real types — arrays, JSONB with indexing, ranges, enums, custom domains.
  • You need strict correctness: proper transactional DDL, thorough constraint checking, no silent coercion.
  • You want the widest managed hosting choice, from Supabase and Neon to RDS, Hetzner and everything between.
  • You expect analytical queries alongside transactional ones, where the query planner's sophistication earns its keep.

Choose SQLite if…

  • The database can live next to the application: a single server, an edge deployment, a desktop or mobile app, a CLI.
  • Read traffic dominates writes, which describes most web applications honestly measured.
  • You want microsecond queries. In-process means no network, no connection pool, no serialisation.
  • You want zero operations: no server to patch, no connection limits, no failover to design.
  • You're building local-first or offline-capable software, where SQLite is effectively the only sane answer.
  • You're testing. Even Postgres-in-production projects benefit from SQLite's instant, disposable test databases — though be careful about dialect differences.

Choose MySQL if…

  • Your team already runs it and runs it well. Operational familiarity beats a feature checklist.
  • Your platform or framework assumes it — a lot of PHP, WordPress and legacy Java tooling does.
  • You need mature, well-understood replication topologies and a deep bench of people who've operated them.
  • You want the cheapest managed hosting; the MySQL market is more commoditised than the Postgres one.
  • You're using a MySQL-compatible distributed engine (PlanetScale, Vitess, TiDB) and want that horizontal scaling path.
  • You're migrating an existing MySQL application and the migration cost outweighs the feature difference.

Concurrency: the real difference between the three

Everything else on this page is detail. The concurrency model is the actual decision.

SQLite allows one writer at a time to the database file. In WAL mode, readers do not block the writer and the writer does not block readers, which removes the historical objection almost entirely for read-heavy workloads. But two simultaneous writes serialise, and if your write transactions are long, the second one waits. Practically this means SQLite is excellent up to hundreds of writes per second on decent hardware and unsuitable for a workload with sustained heavy concurrent writes.

Postgres uses MVCC with process-per-connection. Many writers proceed concurrently, each seeing a consistent snapshot. The cost is that connections are relatively expensive — hence PgBouncer or a built-in pooler on nearly every serious deployment — and that long-running transactions block vacuum, which is the source of most Postgres operational pain in the wild.

MySQL with InnoDB is also MVCC, with a thread-per-connection model that handles large connection counts more gracefully than Postgres does natively. This is a genuine operational advantage if you have thousands of connections and don't want a pooler in the path.

Estimate your peak concurrent writes honestly, not aspirationally. Most applications are far below the threshold where this matters, and a lot of complexity gets bought for a scale that never arrives.

SQLite in production, and the objections that no longer hold

Two things changed the calculus. Modern servers are absurdly fast, and WAL mode plus sensible pragmas removed most of the historical concurrency objection. A single machine with NVMe storage running SQLite will serve a workload that a 2010 engineer would have insisted needed a database cluster.

The tooling caught up too. Litestream streams the WAL continuously to object storage, giving you point-in-time recovery for the price of an S3 bucket. LiteFS and libSQL/Turso offer replication and edge distribution. Several application frameworks now ship SQLite as a first-class production target rather than a development convenience.

What you actually give up: horizontal write scaling, because there is one writer and one file; the ability to run the database on a different machine from the app, which some architectures require; concurrent connections from many application servers; and a rich type system, since SQLite's dynamic typing is permissive and STRICT tables are a relatively recent mitigation.

What you gain: no network latency, no connection pool tuning, no failover design, backups that are a file copy, and a development environment identical to production.

The honest heuristic is that if you deploy to one machine and your writes fit in the thousands per second, SQLite will probably outperform a managed Postgres over a network — and it removes an entire category of operational work. Consider it seriously before dismissing it.

Types, JSON and correctness

Postgres has the strictest type system of the three and will refuse operations the others silently permit. Native arrays, JSONB with GIN indexing and rich operators, range types, proper enums, UUID as a first-class type, and user-defined domains with constraints. If a bad row is worse than a failed insert, this is where Postgres pays you back.

MySQL has narrowed the gap substantially — JSON support is real, strict SQL mode is the default in current versions, and generated columns work well. Older MySQL deployments carry habits from the days of silent truncation and zero dates; the engine has moved on, but a lot of legacy schemas haven't.

SQLite uses dynamic typing: a column declared INTEGER will accept a string unless the table is declared STRICT. Declare STRICT tables on anything new. It also lacks a native boolean and a native date type, storing them as integers and text respectively, which every ORM handles but which occasionally surfaces in raw queries.

For JSON-heavy schemas Postgres is clearly ahead: JSONB is stored in a parsed binary form, indexes well, and has a genuinely good operator set. MySQL's JSON is competent. SQLite's json1 functions work fine for occasional use and are not a document-store substitute.

Operations, hosting and the cost of running each

SQLite has no operations in the usual sense. There is no server, no user management, no connection limits, no failover. Your backup strategy is a file copy or Litestream to object storage. Your scaling strategy is a bigger machine. That is a legitimate strategy up to a surprisingly high ceiling.

Postgres requires more care than its reputation suggests. Autovacuum tuning matters, long transactions cause bloat, connection pooling is effectively mandatory above small scale, and major version upgrades need planning. Managed providers absorb most of this — Supabase, Neon, RDS, Cloud SQL and a dozen others — and the market is competitive enough that prices are reasonable.

MySQL's operational story is the most commoditised. Managed MySQL is cheap and available everywhere, replication is well trodden, and the failure modes are extremely well documented after thirty years. If you want the smallest chance of encountering a problem nobody has written about, this is it.

One thing that should influence the choice more than it does: whichever engine you pick, test your restore. Practise it on a schedule. The difference between a good and a bad database decision is almost never the engine and almost always whether the backup worked.

Frequently asked questions

Is SQLite really OK for a production web app?

Yes, for a large class of them. If you deploy to one machine, your writes are in the hundreds or low thousands per second, and you use WAL mode with Litestream for continuous backup, SQLite is not a compromise — it removes network latency and an entire operational surface.

Should I move from MySQL to Postgres?

Only with a concrete reason: an extension you need, a type system problem you keep hitting, or a hosting platform you want. A migration is weeks of work plus a cutover risk. Feature envy is not a reason.

Which is fastest?

It depends entirely on the workload. SQLite wins on simple reads because there's no network hop. Postgres wins on complex analytical queries because its planner is the best of the three. MySQL is very fast on simple indexed lookups at high connection counts.

What about MariaDB?

A MySQL fork that has diverged over time, with some features MySQL lacks and a different governance model. If you're already on it, staying is fine. For a new project, choose between the three on this page rather than adding a fourth variable.

Can I use SQLite with multiple application servers?

Not against one shared file over a network filesystem — that is the classic way to corrupt a SQLite database. The workable patterns are one writer with read replicas (LiteFS), or a hosted libSQL service such as Turso that puts a protocol in front of the engine.

Which has the best vector search for AI features?

Postgres with pgvector, comfortably. It's mature, it indexes properly with HNSW, and it lets you filter on relational columns in the same query. SQLite has vector extensions that work at small scale; MySQL's story is the weakest of the three.