Data
MySQL
MySQL is the relational database under our PHP and Laravel work, run where the framework and the hosting already assume it.
MySQL is a relational database server, InnoDB by default, and it is what an enormous share of PHP applications assume without ever being asked. Version 8 closed most of the gaps that made people sniffy about it: window functions, common table expressions, real JSON support, atomic DDL. It is a perfectly good database. It is rarely the reason a project chose it.
How we use it
Consonas is the build that runs on it. The application is Laravel 11 and PHP 8.2, with the Saas module handling tenants, packages, quotas, per-tenant databases, subscription histories and trials, so the isolation promise the product makes to buyers is a database per organisation rather than a tenant column on every table. Migrations, permissions and the activity log all come through packages that expect MySQL semantics, spreadsheet import and export goes through the same stack, and Redis sits beside it for cache, sessions and queues. The reason a CRM in this market has to be careful about data isolation and about how much work it demands from a small team is the subject of why three person companies abandon their CRM.
What it costs you
The one that has actually cost us time is DDL. MySQL 8 made individual DDL statements atomic, but a migration file with four statements in it is not one unit of work: statement three failing leaves the first two applied and nothing to roll back to. With a database per tenant that is the failure you meet, because you are running the same migration a few hundred times and one of them will land badly. You end up writing migrations that are safe to re-run and a reconciliation script that reports which tenants are on which version.
Character sets are the other classic. The encoding called utf8 is not UTF-8, it is three bytes and it cannot hold an emoji, so everything has to be utf8mb4, and then the collation decides how uniqueness and sorting behave. Mixing collations across tables produces joins that cannot use an index, which turns up as a mysterious slow query long after the schema was written.
Beyond that: index key length limits bite on long varchar columns, partial and expression indexes are weaker than the Postgres equivalents, and per-tenant databases make information_schema queries slow enough that admin tooling has to cache what it reports. Upgrades between major versions have changed default collations and added reserved words, so a jump is a test-everything exercise rather than a package update.
When we would choose something else
For a new build we reach for PostgreSQL first: transactional DDL alone is worth it once migrations run unattended, and the type system and index options are stronger. We would keep MySQL when the application is Laravel and already working, because a database swap in a mature Laravel codebase touches migrations, raw queries, JSON handling and every full-text search in it, and that budget almost always buys more elsewhere. If the same product were rebuilt on Workers we would look at a pool of SQLite databases on D1 instead, since a database per tenant is native there and there is no server to size.
Where we have used it
Every build below lists this in its stack, so the claim is checkable.