更新日期:2026-09-18
MySQL 8.0 的问题已经不是「还能不能跑」,而是「停止安全修复之后,这套生产库还能被审计地运行多久」。Oracle 官方生命周期页面已经把 8.0 移出常规支持范围,进入只提供有限支持的阶段;具体日期与支持级别随时可能按官方页面调整,不要引用二手博客里的表格。EOL 的实际后果是:新的安全问题不再有官方修复,遇到故障时补丁要么来自扩展支持合同,要么靠自行缓解,成本与风险同时上升。
本文适用于以下环境:Linux 上通过包管理器或官方二进制安装的自建 MySQL 8.0;带主从复制或组复制的生产拓扑;应用连接池可控、能安排维护窗口的团队。托管实例(云数据库)不在本文的直接操作范围内,因为数据目录和二进制由厂商控制,应该走厂商的升级通道,相关差异可参考本站的 阿里云 RDS MySQL 8 使用记录。
本文与常见教程的区别在于顺序:先证明「能回滚」,再证明「能升级」。大版本就地升级在数据字典被改写之后是不可逆的,所以兼容性检查、备份可恢复性、停机窗口、分批切换和回滚路径必须作为一次可审计的变更来设计,而不是登录服务器敲几条命令。如果你刚做过 PostgreSQL 的大版本迁移,流程思想可以直接复用,但 MySQL 的边界条件不同,见 PostgreSQL 14 EOL 迁移指南;升级完成后的存活监控可以结合 Monit 自动重启 MySQL 一起配置。
先看结论
- 先用官方 8.0 系列的最新小版本收口,再排期 8.4 LTS;不要跳过小版本直接做大版本迁移。
- 目标选 8.4 LTS,而不是 8.4 之后的 innovation 创新版本;创新版本发布节奏快,不适合作为长周期生产基线,支持窗口以官方生命周期页面为准。
- 把 mysqlsh -- util check-for-server-upgrade 的输出当作上线门禁:只要还有 ERROR 级别条目,就不安排停机窗口。
- 停机前必须完成一次真实恢复演练:在独立主机上把物理备份恢复起来,并从 binlog 做一次时间点恢复,记录耗时作为 RTO 依据。
- 就地升级前离线留存旧版二进制安装包(deb、rpm 或 tarball)与一份独立物理备份;数据字典一旦升级,旧二进制无法再启动该数据目录。
- 复制拓扑选择「先升从库、再切换主库」:低版本主库可以被高版本从库复制,反过来不成立。
- 变更单里写清硬性中止时间、回滚决策人和回滚触发指标;到点未达标就回滚,不在窗口里现场研究。
- 切换完成后至少观察一个完整业务周期,再清理旧实例、旧备份和旧连接串。
为什么现在必须排期
EOL 不是「数据库会自己停掉」,而是三件事同时发生变化。
第一,安全修复断供。8.0 进入 EOL 之后,新披露的问题不会再有官方补丁;继续运行意味着要么接受已知风险,要么采购扩展支持。对公网可达、存储用户数据的实例,这是审计会上无法回避的问题。
第二,支持成本上升。扩展支持、第三方兜底方案和内部自研补丁的人力成本,通常高于一次规划良好的大版本迁移。把迁移摊到一个季度里做完,比在故障当天被倒逼要便宜得多。
第三,依赖与合规压力。驱动、ORM、连接池、备份工具、监控 exporter、Kubernetes operator 会逐步把测试矩阵收缩到受支持版本;等依赖先放弃 8.0 时,迁移窗口就不再由你决定。托管实例还可能主动推送计划内升级。
目标选 8.4 LTS 的理由很直接:8.4 是官方标记的长期支持版本,支持窗口比创新版本长,社区与厂商工具链也在围绕 LTS 收敛。需要注意两点:一是 8.4 与 8.0 之间确实存在功能移除和默认值变更,见下一节;二是不要照抄本文提到的版本,执行当天必须在官方文档上确认当前 8.4 LTS 的最新小版本与生命周期状态,然后在测试环境完整跑通一遍。
四条迁移路线对比
| 路线 | 停机时间 | 风险 | 回滚难度 | 适用场景 |
|---|---|---|---|---|
| 就地升级(in-place) | 短到中等,约等于一次重启加数据字典升级 | 中到高,升级失败时数据目录已被改写 | 高,必须先有物理备份或快照,无法简单降级 | 数据量中等、能接受停机、驱动与插件都已兼容、有可靠备份的实例 |
| 逻辑复制切换(logical replication cutover) | 极短,只等于最后一次追平加切连接 | 中,受 DDL、无主键表、复制过滤和延迟影响 | 中,切回旧主需要处置已写入新主的数据 | 数据量大、停机窗口敏感、可以提前搭建新版本实例的场景 |
| dump + restore | 长,与数据量和索引重建时间成正比 | 低到中,过程可控但耗时 | 低,旧库原样保留即可回退 | 中小型库、字符集或存储引擎需要顺带整改、允许较长停机的场景 |
| 云托管受控切换(managed controlled failover) | 由厂商流程决定,通常分钟级 | 低到中,细节被厂商封装 | 中,取决于厂商是否保留回切能力 | 使用云数据库、数据目录不可自管、需要厂商支持背书的场景 |
选择逻辑可以压缩成三句话:能停机且有干净备份,就地升级最省事;停机窗口按分钟计,逻辑复制或蓝绿切换更合适;库不大且想顺手整改字符集、存储引擎和表结构,dump + restore 反而最透明。
不适用的场景要提前识别:Galera / Percona XtraDB Cluster 这类多主集群有自己的滚动升级流程;数据目录里存在目标版本不再支持的存储引擎或第三方插件;应用被绑定在即将不可用的认证方式上且客户端无法在窗口内升级。这三种情况都要先解决前置条件,再谈路线选择。
升级前兼容性检查
检查的入口是 MySQL Shell 的 upgrade checker。它在不修改数据的前提下读取数据字典、系统变量、表结构和例程,输出可升级性报告。
# 在跳板机或从库所在主机执行;只读检查,不修改数据
mysqlsh -- util check-for-server-upgrade root@127.0.0.1:3306 \
--target-version=8.4.0 \
--outputFormat=JSON 2>&1 | tee /var/log/mysql/upgrade-check-8.4.json
# 人类可读版本,便于直接贴进变更单
mysqlsh -- util check-for-server-upgrade root@127.0.0.1:3306 \
--target-version=8.4.0 \
--outputFormat=TEXT 2>&1 | tee /var/log/mysql/upgrade-check-8.4.txt
--target-version 应按官方文档填写当前 8.4 LTS 的目标版本,上面的写法只是示例。判断标准是:ERROR 级别条目必须为 0;WARNING 级别条目必须逐条给出「修复、接受、规避」的结论并签字;NOTICE 记录备查。常见 ERROR 包括表使用了不再支持的存储引擎、存在与目标版本冲突的对象定义、字符集或排序规则不受支持、账户仍使用将被移除的认证方式。
第一类,逐条对照官方 removed / deprecated 列表。8.4 移除了一批在 8.0 中已弃用的系统变量、状态变量和旧语法。处理方式是先在测试实例上启动 8.4,读错误日志里的 unknown variable 报错,再从 my.cnf 里删除对应项;依赖这些变量的监控脚本与自动化脚本要同步修改。
第二类,默认认证插件。8.0 起默认插件是 caching_sha2_password,8.4 中 mysql_native_password 默认不再启用。旧客户端(老版本 PHP mysqlnd、老 JDBC 驱动、老版本 GUI 工具、自研 C 客户端)可能直接连不上。建议清单化处理:
-- 盘点账户使用的认证插件
SELECT user, host, plugin FROM mysql.user ORDER BY plugin, user;
-- 单个账户显式指定插件(改前先确认客户端是否支持)
ALTER USER 'app_user'@'10.0.0.%' IDENTIFIED WITH caching_sha2_password BY 'replace-me';
-- 确认连接加密与 RSA 公钥交换能力
SHOW VARIABLES LIKE 'require_secure_transport';
SHOW VARIABLES LIKE 'caching_sha2_password_auto_generate_rsa_keys';
如果客户端在窗口内无法升级,唯一稳妥的做法是先升级客户端,或者把连接改走支持新插件的驱动;不要为了兼容而长期保留不安全的旧认证方式。
第三类,字符集。8.0 中 utf8 是 utf8mb3 的别名,历史表可能仍是三字节实现。升级后应用写入四字节字符(emoji、部分生僻字)会报错或被截断。盘点命令:
SELECT table_schema, table_name, column_name, character_set_name
FROM information_schema.columns
WHERE character_set_name IN ('utf8', 'utf8mb3')
ORDER BY table_schema, table_name, ordinal_position;
SELECT schema_name, default_character_set_name, default_collation_name
FROM information_schema.schemata
WHERE default_character_set_name IN ('utf8', 'utf8mb3');
整改用 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,但这是重建表的重操作,必须在低峰期逐表执行并评估锁等待与磁盘空间;大表建议使用 pt-online-schema-change 或 gh-ost 这类在线工具,并先在从库验证。
第四类,保留字。8.0 相比更早的版本引入了一批新的保留字,自定义函数、存储过程、触发器、视图和 ORM 生成的 SQL 里如果把它们当作裸标识符,升级后会直接语法报错。upgrade checker 会报告使用位置;修复方式是加反引号或改名,并同步应用代码。
第五类,sql_mode 差异。不要依赖默认值,显式对比并统一:
SELECT @@GLOBAL.sql_mode, @@SESSION.sql_mode;
SELECT @@GLOBAL.optimizer_switch;
SELECT @@GLOBAL.innodb_default_row_format;
两端配置必须一致,否则同一条 SQL 在升级前后可能得到不同结果,尤其是除零、零日期、非严格模式插入截断这类行为。
第六类,生成列、函数索引和 JSON。生成列表达式在升级时会重新解析,函数索引本质上是隐藏的生成列;目标版本对表达式的求值规则可能更严格。建议在测试实例上做全表校验(对比行数与校验和),并确认 JSON_TABLE 与 JSON 路径表达式在应用侧的行为一致。
第七类,全文索引。InnoDB 全文索引在升级后可能需要重建,ngram 解析器和停用词表的行为要按官方文档确认;数据量大的表要把重建时间算进窗口预算。
第八类,存储引擎。MyISAM 仍可运行但不具备崩溃安全,升级检查会给出提示;FEDERATED 等引擎需要显式加载插件,升级后可能因为插件未加载而建表失败。建议在迁移窗口前把核心表转成 InnoDB,并确认 innodb_file_per_table 与表空间规划一致。
第九类,权限与授权表。8.0 之后的授权模型包含动态权限、部分撤销等内容,8.4 又调整了部分账户字段与默认行为。迁移后必须重新验证:每个应用账户的最小权限是否仍然成立,是否存在依赖旧字段的运维脚本,以及是否有账户因为认证插件变化而无法登录。
第十类,组复制与复制过滤。组复制要求成员版本一致,官方不支持 8.0 成员与 8.4 成员混跑,必须按官方升级章节整体规划,通常意味着先停组、统一升级、再引导组。复制过滤方面,旧的 replicate-* 启动参数方式应改为 CHANGE REPLICATION FILTER,并确认过滤规则在目标版本上的语义没有变化;GTID、binlog_format、binlog_row_image 等参数要在升级前盘点并评估。
备份与可恢复性验证
备份的目标不是「有文件」,而是「能在可接受时间内恢复到可用状态」。因此需要两条独立链路。
物理备份链路用于快速整库回滚,原理是复制数据文件并记录 binlog 位点,速度与数据量近似线性。可用手段包括文件系统快照、云盘快照、Percona XtraBackup 或 MySQL Enterprise Backup;具体支持矩阵以各工具官方文档为准,不要凭经验假设某个版本组合可用。
# 记录位点,便于后续 PITR
mysql -e "SELECT @@GLOBAL.gtid_executed; SHOW MASTER STATUS;" \
> /backup/gtid-and-pos-$(date +%F-%H%M).txt
# 逻辑备份:一致性快照 + GTID 信息 + 例程与事件
mysqldump --single-transaction --routines --triggers --events \
--set-gtid-purged=ON --hex-blob --default-character-set=utf8mb4 \
--all-databases > /backup/all-db-$(date +%F-%H%M).sql
# 校验和写入备份记录,与备份文件分开存放
sha256sum /backup/all-db-*.sql > /backup/all-db-$(date +%F-%H%M).sha256
逻辑备份用于跨版本导入、单库单表修复和字符集整改验证;物理备份用于整机快速恢复。两者都要做校验和,且校验和文件要与备份文件放在不同位置,避免同一次磁盘故障把两者一起带走。
恢复演练的流程必须是「在独立主机上真的恢复一次」:
- 准备一台与生产配置接近、磁盘空间充足、网络隔离的机器。
- 用物理备份完整恢复,记录从开始到 mysqld 可提供服务的时间,这就是实际 RTO。
- 用 mysqlbinlog 从备份位点重放到某个时间点,验证 PITR 链路可用。
- 在恢复实例上做业务侧只读校验:关键表行数、金额汇总、最近订单抽样。
- 把演练耗时、发现的坑、需要的额外磁盘写入变更单。
在变更单里同时定义 RPO 与 RTO:RTO 决定你必须用物理备份还是快照,RPO 决定 binlog 必须保留多久。如果 RPO 是「最多丢五分钟」,那么 binlog 的保留时间必须显著大于最长故障发现时间,并且要定期确认 binlog 没有被自动清理策略提前删掉。抽查命令:
mysql -e "SHOW VARIABLES LIKE 'log_bin';"
mysql -e "SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';"
mysql -e "SHOW BINARY LOGS;" | tail -5
需要清理 binlog 时属于会改变生产状态的破坏性操作,前置条件是确认所有从库与备份链路都已消费到目标位点,验证方法是对比各从库的 Executed_Gtid_Set,并且要先在测试实例上验证命令行为。删早了,PITR 能力就没了。
没有恢复过的备份不算备份。常见失败原因包括:备份期间有 DDL 导致一致性断裂、binlog 已被清理导致无法 PITR、恢复主机缺少同名账户或插件目录、innodb_log_file_size 等参数与备份不匹配导致启动失败。这些问题必须在上线前暴露,而不是在回滚那一刻才第一次遇到。
停机窗口设计与业务降级
窗口设计的关键是把「业务可感知的中断」压到最短,同时给升级操作留出足够缓冲。可组合使用的手段:
- 只读模式:先 SET GLOBAL read_only = ON,确认没有写入后再 SET GLOBAL super_read_only = ON,避免高权限账户绕过。
- 连接池排空:先摘除写入流量,再等连接池空闲连接自然回收,必要时重启连接池进程,防止旧连接继续写入。
- 维护页:静态维护页与数据库升级解耦,前端先切维护页再动数据库,避免升级中途用户看到报错页。
- DNS 与 TTL:提前把数据库域名 TTL 降到可接受的低值,应用统一使用域名而非 IP,切换时减少等待。
- 队列与异步任务:先停消费者再停生产者,或让消息堆积在队列里,升级完成后按顺序回放,避免重试风暴打满新实例。
- 时长估算:数据字典升级耗时与表数量、表空间文件数、生成列和全文索引数量相关,用同规模测试实例的恢复演练来估算,不要凭感觉。
- 窗口选择:选业务低峰,并留出至少一倍于估算时长的缓冲;如果估算时长超过窗口,就换路线,而不是压缩验证步骤。
- 硬性中止时间:变更单写明「到几点几分必须开始回滚」,到点即执行,不在窗口里临时排障。
- 沟通:提前通知客服、运营和上下游调用方,准备好回滚后的对外话术。
就地升级执行步骤
以下步骤假设你已经通过 upgrade checker、完成恢复演练并在测试环境验证。所有会改变生产状态的命令都要先演练,并确认回滚命令可用。
第一步,前置检查与收口:
# 确认无长事务、无延迟从库、无未完成的 DDL
mysql -e "SELECT * FROM information_schema.innodb_trx\G"
mysql -e "SHOW REPLICA STATUS\G" | grep -E "Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source"
mysql -e "SELECT * FROM performance_schema.events_statements_current WHERE SQL_TEXT IS NOT NULL\G"
第二步,停写并做最终备份:
# 只读 + 排空写入
mysql -e "SET GLOBAL read_only = ON;"
mysql -e "SHOW GLOBAL VARIABLES LIKE 'read_only';"
mysql -e "SET GLOBAL super_read_only = ON;"
# 最终位点与物理备份(备份命令按你实际使用的工具执行)
mysql -e "SHOW MASTER STATUS\G" | tee /backup/final-pos.txt
# 例如使用 XtraBackup:xtrabackup --backup --target-dir=/backup/inplace-final
# 干净关闭,避免启动时做崩溃恢复
mysqladmin shutdown
第三步,安装 8.4 服务端包。Debian / Ubuntu 需要先把官方 APT 源切到 8.4 再安装;RHEL 系需要先配置对应 DNF 源。
# Debian / Ubuntu
dpkg -l | grep -E "^ii mysql-(server|client|community)" | tee /root/mysql-pkgs-before.txt
apt-get update
apt-get install --only-upgrade mysql-community-server
mysqld --version
# RHEL / Rocky / AlmaLinux
rpm -qa | grep -i mysql | sort | tee /root/mysql-pkgs-before.txt
dnf clean all
dnf install mysql-community-server
mysqld --version
第四步,启动并观察数据字典升级。8.0.16 之后的版本会在启动时自动完成数据字典升级,正常日志会出现升级开始与完成的记录;如果启动失败,先看错误日志,不要反复重启。
systemctl start mysqld
journalctl -u mysqld -n 200 --no-pager | grep -iE "upgrad|error|dictionary"
# 正常分支:版本正确、升级完成、账户可登录
mysql -e "SELECT VERSION(); SHOW GLOBAL VARIABLES LIKE 'read_only';"
mysqlsh -- util check-for-server-upgrade root@127.0.0.1:3306 --target-version=8.4.0
# 异常分支:启动失败或升级报错,立即停止后续操作并评估回滚
systemctl status mysqld --no-pager
tail -n 200 /var/log/mysql/error.log
第五步,解除只读前先做最小验证:建表、写入、事务回滚、写入四字节字符、调用一个使用生成列或全文索引的查询。全部通过后再执行 SET GLOBAL super_read_only = OFF; SET GLOBAL read_only = OFF;,然后按批次放量。
异常分支的处理原则是:只要已经用新版服务端启动过该数据目录,就不要尝试用旧二进制再启动它,直接走「物理备份恢复 + binlog 重放」的回滚路径。
复制拓扑切换步骤
复制拓扑的升级顺序是「先升从库,再切主库」,因为 MySQL 支持从低版本复制到高版本,不支持从高版本复制到低版本。
- 选一台从库,停止其复制线程,升级到 8.4,启动后恢复复制。
- 用延迟与错误指标确认追平:
mysql -e "SHOW REPLICA STATUS\G" | grep -E "Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source|Last_Error"
mysql -e "SELECT * FROM performance_schema.replication_applier_status_by_worker\G"
- 追平后升级其余从库;每次只动一台,并保持至少一台 8.0 从库作为退路。
- 准备切换:停写、等待所有从库 Seconds_Behind_Source 归零,并核对 GTID 集合是否一致。
- 选一台 8.4 从库提升为新主库。先停 IO 线程并核对已接收与已执行的 GTID 集合,确认没有残留事务后再清理复制元数据、关闭只读:
# 在准备提升的 8.4 从库上执行;这是改变生产状态的步骤,需前置确认已停写
mysql -e "STOP REPLICA IO_THREAD;"
mysql -e "SHOW REPLICA STATUS\G" | grep -E "Retrieved_Gtid_Set|Executed_Gtid_Set|Seconds_Behind_Source"
mysql -e "STOP REPLICA;"
mysql -e "RESET REPLICA ALL;"
mysql -e "SET GLOBAL super_read_only = OFF; SET GLOBAL read_only = OFF;"
- 应用连接切换优先改代理、服务发现或配置中心,避免同时修改大量应用实例。
- 旧主库不能再作为新主库的从库(版本方向不允许)。它的处置方式只能是走物理备份重建为 8.4 从库,或者在下线前保持只读作为应急查询副本。
从库追不上时的处理顺序:先看 Last_Error 与 applier worker 状态,区分是单条语句报错、复制过滤不匹配、大事务,还是磁盘与网络瓶颈。大事务可以等待,报错必须定位到具体 SQL 并在确认业务影响后决定跳过或中止。到达硬性中止时间仍未追平,就放弃切换、把应用连接退回旧主库继续服务,并重新排期。
升级后验证清单
先跑一遍自动化的技术验证,再按清单逐项人工确认:
# 字符集与关键参数
mysql -e "SELECT @@character_set_server, @@collation_server, @@character_set_client;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time';"
# 复制状态
mysql -e "SHOW REPLICA STATUS\G" | grep -E "Replica_IO_Running|Replica_SQL_Running|Seconds_Behind_Source"
# 业务侧行数抽样,与升级前基线对比
mysql -e "SELECT COUNT(*) FROM app.orders WHERE created_at >= CURDATE() - INTERVAL 1 DAY;"
- 版本与编译信息符合预期:SELECT VERSION();
- 应用账户可以登录,没有账户因认证插件变化而失败
- read_only 与 super_read_only 已按预期关闭
- 主从复制状态正常,无错误、无持续延迟
- 数据库、表、连接的字符集均为 utf8mb4
- 慢查询日志已开启,long_query_time 与升级前一致
- 关键 SQL 的执行计划没有出现全表扫描等退化
- 性能抽样:QPS、P99 延迟、缓冲池命中率与升级前基线对比无显著劣化
- 应用冒烟测试通过:登录、下单、支付回调、后台任务、报表导出
- 备份任务已指向新实例并成功跑完一次,恢复演练计划已更新
- 监控、告警、exporter 采集正常,没有 unknown variable 类报错
- 旧二进制包与最终备份在观察期内保持可用
回滚方案与不适用场景
就地升级的回滚不是「降级」。MySQL 的大版本之间不存在把已升级的数据目录用旧二进制打开的路径;一旦目标版本完成数据字典升级,8.0 的 mysqld 无法再启动该目录。所谓回滚,本质是「用升级前的物理备份或一致性快照重建 8.0 实例,再重放 binlog 到故障点之前」。
要做成这件事,必须提前满足四个条件:
- 升级前有完整的物理备份或一致性快照,且已验证可恢复。
- binlog 在升级前后都没有被清理,并且保存在与数据目录不同的位置。
- 旧版本二进制安装包离线留存,不依赖源仓库还能拉到旧版本。
- 回滚的触发指标、决策人和硬性时间点已写入变更单。
如果这四条无法满足,就不要选就地升级,改用逻辑复制切换:新旧实例并行运行,旧实例保持可写直到切换完成,切换后仍可作为回滚目标,只要能处理切换后写入新库的增量数据。
明确不适用的场景:
- 数据目录由云厂商控制的托管实例:禁止自行替换二进制或直接操作数据目录,必须走厂商升级流程。
- Galera / Percona XtraDB Cluster 等多主集群:滚动升级有独立流程,混用本文步骤可能导致集群分裂。
- 应用依赖即将不可用的旧认证方式且客户端在窗口内无法升级:先解决客户端,再谈数据库。
- 数据库中存在官方已移除的存储引擎或无法升级的第三方插件。
- 单实例数据量导致备份恢复时间远超可接受停机窗口,且没有快照能力:应优先逻辑复制或蓝绿切换。
官方资料与继续阅读
- MySQL 8.4 Reference Manual
- MySQL 8.4 Release Notes
- Upgrading MySQL
- MySQL Shell:Upgrade Checker Utility
- MySQL:Changes in MySQL 8.4(移除与弃用清单)
- MySQL Product Support EOL Notice
站内相关文章:


