MySQL10 min read

MySQL 5.7 to 8.0 Upgrade Field Notes for the Team That Waited

MySQL 5.7 has been end-of-life since October 2023, and plenty of production fleets still run it. Field notes on the upgrade checker, the removals that actually break apps, and a rollback plan that is honest about being a restore plan.

MySQL 5.7 reached end of life in October 2023, and I still walk into shops running it in production. Nobody is proud of it; there is always a reason, an ancient ORM, a "we will do it next quarter" that has survived eight quarters, a fear of the collation stories they heard secondhand. This post is for the team finally doing it: the field notes I wish someone had handed me before my first 5.7 to 8.0 upgrade, with the failure modes ranked by how often they actually bite.

One framing note up front: the path is 5.7 to 8.0, then 8.0 to 8.4 as a separate project. There is no supported direct jump from 5.7 to 8.4, so the upgrade you have been deferring is actually the first of two.

This upgrade is different in kind, not degree

Minor upgrades in the 5.x era were mostly binary swaps. The 8.0 upgrade rebuilds the foundations: the data dictionary moves from FRM files into transactional InnoDB tables, and once a server has started on 8.0 and rewritten its metadata, there is no supported downgrade. Not "downgrade is fiddly." There is no in-place path back at all. Every decision below flows from that fact, and it is why the rollback section of this post is really a restore section.

The good news from the same change: the server handles its own metadata upgrade at startup on modern 8.0 releases, so the old separate mysql_upgrade step is gone. The bad news: because startup performs the upgrade, an incompatibility you did not catch beforehand surfaces as a server that will not start, which is the worst possible place to learn about it.

It is also worth saying out loud what the upgrade buys, because the team doing this work deserves a reason beyond "5.7 is EOL." Window functions and common table expressions alone modernize a decade of awkward workaround SQL. Atomic DDL removes a whole class of half-applied-migration incidents. Instant column addition turns some of the scariest ALTERs into metadata changes. Invisible indexes let you test dropping an index without dropping it. Roles clean up grant sprawl. None of that helps you during the migration, but all of it helps every week after.

Run the upgrade checker until it is boring

MySQL Shell ships a purpose-built preflight: util.checkForServerUpgrade. Point it at the running 5.7 server and it reports removed features in use, orphaned or conflicting schema objects, obsolete sql_mode flags, reserved-word collisions, partitioned tables on non-native engines, and more. Run it early, fix what it finds, and run it again after every fix cycle until the report is boring. It runs from the Shell client, so nothing is installed on the database host.

The checker is necessary but not sufficient. It inspects the server; it cannot inspect your application's SQL, your connector versions, or your assumptions about implicit behavior. The sections below are the gaps I have watched it not catch.

The query cache is gone, and your graphs will change

The query cache was removed outright in 8.0, not deprecated, removed, along with its variables and status counters. For most workloads this is addition by subtraction: the cache's global mutex made it a scalability bottleneck, and most serious deployments had already set query_cache_type to zero years earlier.

The workloads that hurt are the ones quietly leaning on it: read-heavy apps with highly repetitive identical statements and low write rates, often older PHP monoliths. If that is you, the honest fixes are application-side caching or a caching proxy layer, not grief. Either way, expect your latency profile to change shape after the upgrade, which is a monitoring problem as much as a database one: capture a before-and-after view of your top statements, using the approach from the slow query log vs pg_stat_statements notes, so regressions are attributable instead of anecdotal.

Charset and collation shifts: the slowest-burning problem

8.0 changes the default character set to utf8mb4 and the default collation to utf8mb4_0900_ai_ci. Your existing tables keep their collations through the upgrade, but new tables, new columns, and fresh connections pick up the new defaults. The result is a mixed-collation estate that works fine until a join compares a utf8mb4_general_ci column against a utf8mb4_0900_ai_ci one, at which point you get either an "illegal mix of collations" error or, more insidiously, a comparison that cannot use the index because one side needs conversion.

Take an inventory before the upgrade and decide on a target collation deliberately.

SELECT table_schema,
       ccsa.character_set_name,
       t.table_collation,
       COUNT(*) AS tables
FROM information_schema.tables t
JOIN information_schema.collation_character_set_applicability ccsa
  ON t.table_collation = ccsa.collation_name
WHERE t.table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
GROUP BY table_schema, ccsa.character_set_name, t.table_collation
ORDER BY tables DESC;

Converting large tables to a new collation is an online-DDL project of its own, so most teams pin server and database defaults to match their existing collation at upgrade time, then converge later. Both choices are defensible; making no choice is how you end up debugging index-ignoring joins in production six months later.

Reserved words and the small breakage tour

8.0 introduced window functions, which means RANK, LAG, LEAD, ROW_NUMBER, GROUPS, and friends became reserved or gained meaning. A 5.7 schema with a column literally named rank, common in anything with scoring, breaks on unquoted references. Scan for collisions ahead of time.

SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE LOWER(column_name) IN
      ('rank', 'groups', 'lag', 'lead', 'row_number',
       'dense_rank', 'cume_dist', 'ntile', 'lateral', 'system')
  AND table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema');

The rest of the small-breakage tour, each item cheap to check and miserable to discover live: the NO_AUTO_CREATE_USER sql_mode is gone, and any stored procedure or dump carrying it in a definition fails; GRANT no longer implicitly creates users, so provisioning scripts need an explicit CREATE USER; the ASC and DESC modifiers on GROUP BY were removed; and partitioned tables must use an engine with native partitioning, which in practice means InnoDB. Finally, the default authentication plugin changed to caching_sha2_password, and connectors old enough to only speak mysql_native_password fail to connect at all. Test every client library version you run, not just the newest one, and remember the deadline behind the deadline: mysql_native_password is disabled by default in 8.4, so fixing connectors properly now pays twice.

A rollback strategy that is honest

Since in-place downgrade does not exist, rollback means one of two things: restore 5.7 from backup and accept losing the writes made on 8.0, or never put yourself in that position. The pattern I use is the second one. Upgrade a replica to 8.0 and let it replicate from the 5.7 primary for days, replication upward from 5.7 to 8.0 is supported for exactly this purpose, and run your read traffic and your regression comparisons against it. When confidence is earned, cut over by promoting the 8.0 side during a controlled window, and keep the old 5.7 primary stopped but intact, with its final backup and binlogs, as the rollback artifact.

After promotion, rolling back means restoring the 5.7 environment and replaying or accepting the loss of the delta, so the real strategy is to make the pre-promotion phase long enough that you never exercise it. And rehearse the whole sequence, upgrade, comparison, promotion, on a staging copy restored from production backups before touching the real topology; the rehearsal finds the connector you forgot and the cron job pointed at the old primary, at a price of hours instead of an incident.

Budget monitoring into the cutover plan as a deliverable, not an afterthought. Baseline your top query digests, error rates, and connection behavior on both sides of the replication pair while they run in parallel, because "the upgrade made things slow" claims arrive within hours of promotion and need evidence, not vibes. The optimizer genuinely changes across this boundary, some plans improve, a few regress, and the difference between a calm week-one and a panicked one is whether you can name the specific query families that moved and by how much.

Where MonPG fits today

Honesty about our own product: MonPG is a PostgreSQL monitoring platform, and it does not monitor MySQL today. MySQL monitoring (coming soon) is in active development, and upgrade situations like this one, where you need before-and-after workload evidence across two server versions, are a core scenario we are designing for. If your fleet also includes PostgreSQL, the platform is live there and you can start there today. Whatever tooling you use for the MySQL side, do not skip the baseline: the teams that suffer least from this upgrade are the ones who could prove what "normal" looked like before they touched anything.