事情还得从上个月说起。我手头维护的那套业务库里,有几台 MySQL 5.7 实例已经"超龄服役"很久了。监控后台每隔几天就弹一次版本过期的提示,群里的小伙伴一看到这种告警就开始互相甩锅——"EOL 了,升级吗?""没预算,先扛着吧"。说实话,MySQL 5.7 走到生命周期尽头这件事,技术圈里早就不是新闻了,可真正落到你负责的数据库上,那种焦虑是完全不一样的。尤其是随着 8.0 的非 LTS 创新版节奏越来越快,以及 8.4 这一条新 LTS 主线已经出现,摆在大多数团队面前的其实根本不是"要不要升级",而是"升级到哪个版本、怎么平稳升级、途中会踩到哪些雷"。
这篇内容就是我这次从 MySQL 5.7/8.0 向 MySQL 8.4 升级的完整记录,包括 EOL 背景下的版本选型逻辑、升级前的体检清单、正式切换的操作细节,以及升级后撞上一个官方 Bug 的完整排查链路。整个过程谈不上多惊心动魄,但踩过的坑、绕过的弯、最终确认是官方问题的那种"终于松了口气"的心路历程,我觉得都值得分享出来。如果你也正在为 MySQL 8.4 升级做准备,或者在犹豫要不要升级、怎么排期,这篇文章应该能帮你省下不少试错成本。
1. 为什么是 8.4:EOL 节点带来的选择题
1.1 先看时间线:5.7 和 8.0 到底急不急
MySQL 社区版的维护节奏,很多人直到升级前都没彻底弄明白。简单来说,MySQL 5.7 在 2023 年 10 月就已经到达生命周期终点,意味着社区版不再有常规的 bug 修复和安全补丁。虽然有的云厂商或者付费订阅还能提供延保,但那本质上是在用商业合同帮你"续命",一旦合同到期或者厂商策略调整,你手上的老版本就真的裸奔了。
MySQL 8.0 的情况稍微特殊一点,它原本也有一张 EOL 的时间表,但后续因为各种原因,官方把维护终点做了一定延长。这里我不打算纠缠具体年月,大家只要记住一个核心逻辑:8.0 这条产品线的定位是"创新版",也就是功能迭代快、发布节奏快,但长期维护承诺不如 LTS 版本清晰。而 MySQL 8.4 是官方重新划分产品线之后首条 LTS(Long Term Support)版本,它的维护周期更长,更适合企业级生产环境。
说白了,当 5.7 已经彻底退场、8.0 逐渐走向"新功能试验田"定位的时候,8.4 就是那个"长期饭票"。我当时的判断很简单:与其在一个快要进入维护尾声的版本上反复横跳,不如直接瞄准 8.4,一次性把升级路线定在一条稳定的 LTS 主线上。
1.2 为什么我不建议守着 8.0 不动
8.0 当然不是不能用,它已经经历了多个小版本的打磨,稳定性非常不错。但这里有个容易被忽略的问题:如果你现在是从 5.7 升到 8.0,那你两年后大概率还得再来一次升级,因为 8.0 后续的维护承诺并不像 LTS 那样让人安心。升级这件事最贵的从来不是软件授权费,而是人力成本、业务适配成本、回归测试成本,以及半夜钉钉群里"数据库连不上了"的惊恐三分钟。
所以我在选型阶段就定下了一个原则:一步到位,升级到 MySQL 8.4 LTS。这样未来三到五年内,我都不用再为"这个版本还安不安全、官方还管不管"这类问题操心了。MySQL 8.4 相比 8.0 也做了一些默认行为和参数层面的优化,比如更严格的密码认证插件默认策略、更规范的错误日志体系,以及对一些旧参数的清理。这些变化在带来安全提升的同时,也意味着升级不是简单换一个二进制文件就能搞定的,你需要认真对待每一条"不兼容变更"。
1.3 升级前先给自己算的三笔账
在正式动手之前,我建议大家先别急着下载安装包,先把三笔账算清楚:
第一笔是存量资产的账。你到底有多少台实例、每台实例上有多少个库、最大的表有多大、有没有用到存储过程、触发器和自定义函数、有没有老版本特有的参数配置?这些信息决定了升级的工作量和风险边界。
第二笔是兼容性的账。MySQL 8.4 移除了部分在 8.0 早期就标记为废弃的功能特性,比如 mysql_native_password 的默认禁用、部分旧 SQL 语法的行为变化。如果你的业务代码里还带着老旧的连接字符串或者特殊语法,升级之后很容易出现"能启动但连不上"或者"能连上但跑不动"的尴尬局面。
第三笔是回滚能力的账。升级不是只能进不能退,但回滚的难度跟你在升级前做了多少准备成正比。没有全量备份、没有保留旧版本的完整配置、没有做过恢复演练,那么所谓的"回滚预案"基本是句空话。
这三笔账算完,你对升级的焦虑应该就能转化为一张任务清单了。
2. 升级前哨:把"不会出问题"变成可验证的清单
2.1 我们实际执行的兼容性检查
升级这件事,最怕的就是"我觉得没问题"。我们当时的做法是专门搞了一个兼容性检查阶段,哪怕业务方催得再急,这个步骤也绝对不能省。
首先是用官方工具或脚本扫描所有实例的配置项,重点排查那些在 MySQL 8.4 里已经删除或改名的参数。比如 8.4 里日志系统的参数从 log_warnings 改为 log_error_verbosity,如果你沿用旧参数,轻则告警刷屏,重则启动失败。我们还检查了所有用户的认证插件,确认没有依赖 mysql_native_password 的存量账号——这一步非常关键,因为 8.4 默认已经不再支持这种旧式认证机制,如果你的中间件或者老客户端不支持 caching_sha2_password,升级后就会出现大量认证失败。
其次是 SQL 层面的回归。我们把业务侧的慢查询日志、审计日志都拉了出来,整理出一份高频 SQL 清单,在测试环境的 MySQL 8.4 实例上逐条跑了一遍。有些 SQL 在 5.7 里能正常执行,到 8.4 会因为优化器行为变化而选择完全不同的执行计划。不是说新的执行计划一定更差,但你必须提前知道,否则上线后突然冒出来的慢查询会让你措手不及。
最后是检查插件和系统表。MySQL 8.4 不再需要 mysql_upgrade 这个传统步骤,因为数据字典升级会在启动时自动完成。但这也意味着你必须提前确认磁盘空间充足、系统表没有损坏。我们就在一台测试机上遇到过因为 binlog 文件占满磁盘,导致启动时数据字典升级中断的幺蛾子。
2.2 备份策略与恢复演练不能走过场
升级前的全量备份,谁都会做,但很多人做完备份就再也不管了,直到出问题时才后悔——备份文件能不能用、恢复流程要走多久、恢复后数据是否一致,这些都必须提前验证。
我的建议是至少在测试环境做一次完整的备份恢复演练,并且要在恢复出来的实例上跑一遍核心接口的冒烟测试。光有 dump 文件是不够的,你要确保备份链路本身是通的。我们当时用的是物理备份加 binlog 增量归档的组合方案,在演练中模拟了"备份库被误删、恢复到故障前一秒"的场景,全程跑完大概花了一个多小时。这个时间数据等,能让你心里真正有底。
2.3 跨版本升级路径怎么走
还有一个特别容易被忽略的细节:从 5.7 直接跳到 8.4 是不被官方推荐的跨大版本升级路径。稳妥的做法是先升级到 8.0 的最新小版本,确认数据字典和表结构完成升级后,再往 8.4 走。这不是多此一举,而是因为 8.4 的数据字典结构建立在 8.0 的演进基础上,跳过中间版本风险太高。
我们当时因为部分实例本来就是 8.0,所以少走了一段路。但有 5.7 老实例的同事确实按照"5.7 -> 8.0.x -> 8.4"的两步走方式操作的,每一步都做一次数据字典升级和业务验证。听上去麻烦,但事后证明这个稳扎稳打的做法非常值得,因为每一步的差异都比较好定位。
3. 正式升级的执行细节:从预检、替换到切换
3.1 停机窗口怎么安排才不慌
升级总归要有一个窗口期,关键是窗口期怎么留。我们的做法是选择业务低峰期的凌晨,并且把窗口长度设定为预计操作时间的两倍以上。比如预估切换需要两个小时,那窗口就按四个小时申请,多出来的时间用来做紧急排障和回滚。
窗口前两小时,我们还要做一轮最终的预检:确认所有从库追上了主库、延迟为 0,确认 binlog 没有异常堆积,确认备份文件已经完整拷贝到独立存储。预检通过后,才真正进入操作流程。
3.2 主从切换式的升级步骤
升级方式上,我强烈建议不要在主库上原地升级,除非你的业务量非常小、可以接受长时间停机。我们用的是"先升级从库、再主从切换"的方案。
具体步骤大致是这样的:
- 在从库上停掉复制服务,把新版本的二进制包解压替换到位;
- 启动从库实例,让 MySQL 自动完成数据字典升级;
- 检查错误日志,确认没有致命错误后,重新拉起复制;
- 观察新版本从库的复制状态、延迟、错误日志,至少稳住一个小时;
- 在主库配置不变的情况下,把读写流量切到新版本从库上,原主库降级为新从库或备用节点;
- 原实例再按同样流程升级,重新接入集群。
这个方案的好处在于,任何一步不对劲,流量还能切回去,不需要面对"已经升级到一半、业务完全停摆"的绝望局面。
3.3 升级后的第一轮验证清单
切换完成后,别急着收工,我总结了一个"升级后三件事"的清单,每次都用得上:
第一件事,核对版本和全局变量。SELECT VERSION() 必须是指定的小版本号,关键参数如 transaction_isolation、innodb_buffer_pool_size、max_connections 要和预期一致。配置文件里的废弃参数是否被正常忽略,也要看日志确认。
第二件事,检查所有账号的认证插件和权限。8.4 对权限表的校验更严格,有些旧账号可能因为认证插件不受支持而失联。我们当时就发现一个老应用账号连不上了,后来重新创建账号并授权才恢复,这个坑如果你不提前排查,切换后的电话会被打爆。
第三件事,做一轮核心业务 SQL 的压测。不需要全量,但一定要覆盖读多写少、事务写、批量导入这三类典型场景。通过对比升级前后的延迟曲线和慢查询分布,基本就能判断新版本在你的业务模型下是否正常。
4. 深度踩坑:一个官方 Bug 的完整排查链路
4.1 现象:先是复制延迟,然后是错误结果
升级后的第一周,一切安静得让人有点不踏实。直到某天下午,监控告警突然弹出一条消息:某从库的 SQL 线程报错,复制延迟开始持续堆积。我当时第一反应是"不会升级的时候把 binlog 搞出问题了吧",于是赶紧登录从库查看 error log,结果只看到两条非常笼统的提示,大意是执行某个事件失败,和一个外键关联相关的报错。
这里要说明一下业务背景。我们有一个统计类业务,会定期从主业务库同步数据到报表库,中间用了几张带外键约束的分表。报错指向的正是其中一张分表的更新语句,提示"违反外键约束"。但诡异的是,我手工去主库执行同样的更新语句,它跑得又快又好,根本不报错。而且从库的 SQL 线程一旦停下,后面的 binlog 又只能干等。我们当时一度怀疑是升级过程中数据不一致导致的。
4.2 第一轮排查:从数据一致性开始,却越查越糊涂
按常规思路,我想先确认主从数据是否不一致。于是用工具对那张分表做了全量校验,结果竟然一切正常,所有行的内容和主库完全一致。那问题来了,数据没问题,为什么 SQL 线程会报外键约束失败?
我怀疑是不是外键元数据有问题,于是查看了表结构,外键定义都还在,指向的主表也没问题。接着我尝试在从库手动执行那条 update,居然也能成功,主键和外键都能对上。这下事情变得有趣了——同样是数据,跑在 SQL 线程里就报错,手动执行就正常。唯一合理的解释是,SQL 线程在应用 binlog 事件时,走了和手工执行完全不同的代码路径,而这条路径恰好触发了某个逻辑错误。
4.3 定位:EXPLAIN 结果和官方 Bug 的吻合
为了进一步确认,我在从库上手工执行了和报错语句相似但加了 EXPLAIN 的查询,结果赫然发现优化器选择的执行计划和主库不一样,预估行数偏差非常夸张,甚至选错了驱动表。当时我脑子里某个开关就"啪"地一下被打开了——这大概率不是数据问题,而是优化器或者索引统计信息出了问题。
我立刻去查 information_schema.STATISTICS 这类统计视图,发现那张分表的统计信息存在异常的陈旧状态,即使执行了 ANALYZE TABLE 和重建索引,只能管一两天,之后又会复发。折腾到这一步,我在官方 Bug 追踪系统里搜索了 MySQL 8.4 关于 Instant 列变更、索引统计更新的相关条目,果然找到一条和我们场景高度吻合的官方确认问题:在特定条件下,执行在线 DDL(快速加列)之后,表上的二级索引统计信息可能长时间不刷新,导致优化器生成错误计划,在复制 SQL 线程的回放路径上甚至表现为串行执行同一行时出现逻辑错误。
官方 Bug 确认后,我反而松了口气,因为这意味着问题不是我们数据坏掉或者操作失误,而是一个上游版本的已知缺陷。随后我们按照官方给出的指引,先给从库的异常 SQL 强制指定了索引,恢复复制,再在下一个维护窗口把实例升级到包含修复的补丁小版本。补丁升级后,我们连续观察了两周,那条 SQL 的执行计划恢复稳定,复制延迟也降到了毫秒级。
4.4 这段排查给我留下的经验
这次排查前后花了将近三天,复盘下来有几条经验很值钱:
第一,遇到"数据一样但结果不一样"的问题,优先怀疑优化器和统计信息,而不是一上来就抄起一致性校验工具穷跑一遍。因为如果逻辑层一致,物理层几乎不可能自己坏掉。
第二,EXPLAIN 是你的好朋友,但不只是在写 SQL 的时候用。当复制回放报错时,同样可以拿报错的那条日志,去目标库上手动执行并对比计划差异。
第三,官方 Bug 不是洪水猛兽,反而是一根救命稻草。对小团队来说,能和官方行为对得上,意味着你不需要自己去研究那些深不可测的 InnoDB 内部逻辑,等补丁就行了。当然,"等补丁"不等于"裸奔等",临时用 force index、修改 SQL 方式绕过,是必须同时做的。
5. 升级后的稳定性运营与回滚预案
5.1 升级后一周重点盯的监控指标
升级后的一周,我建议把监控粒度调得更细一些。除了常规的 CPU、内存、磁盘和连接数之外,有几个指标特别值得盯:
第一个是复制延迟。主从延迟突然拉高,往往不是网络问题,而是新版本在某个特殊负载下出现了性能回退或者 bug。我们这次如果不是一直盯着延迟曲线,那个 SQL 线程的报错可能还要晚几个小时才会被发现。
第二个是锁等待和死锁。8.4 的事务调度和行锁实现虽然沿用了 InnoDB 的成熟机制,但和旧版本仍可能存在细微差异,尤其是并发写多的业务,要留意锁等待直方图的变化。
第三个是慢查询分布。升级后执行计划的改变,会让一些原本很快的 SQL 突然变慢。监控里不能只看慢查询总数,还要看 TOP N 和耗时分布,每天对比一次,连续一周基本就能把异常暴露完。
5.2 回滚:不是"不行就回"那么简单
回滚预案很多人写了,但真到要用的时候才发现漏洞百出。比如你升级完成后,新版本继续写了新的 binlog,旧版本的实例即使还留着,也没有办法直接接手这些新数据。所以回滚预案的核心不只是"把程序换回去",还要考虑数据回放和数据一致性。
我们当时的做法是:保留一套未升级的旧版本从库,也就是常说的"金丝雀节点",在整个观察期内让它一直作为只读备机存在,持续接收主库的 binlog 备份但不回放。一旦新版本出了问题,我们就把它作为替换节点,配合备份增量恢复,把业务切回去。这个方案听起来奢侈,但能最大程度地保住数据可用性。
5.3 长线配置调整:从默认值到贴合业务
升级到 8.4 之后,别急着把所有参数调成"高性能模式"。我们遇到过不少团队,升级后马上把 buffer pool 调到很大、把 binlog 刷盘策略改成最激进模式,结果性能没提升,反而出现大量的 IO 抖动。我的建议是先用默认参数跑两三天,让统计数据和业务负载回归正常,再基于监控数据做渐进式调整。
比如 innodb_buffer_pool_size,可以按实际命中率来调;binlog 相关参数,可以根据同步延迟和磁盘 IO 负载来调整;连接数则按峰值并发和连接复用率来设置。记住一个原则:8.4 的默认值已经是一个很合理的起跑线,你的业务给了我充足的数据,再去动参数,而不是照搬网上流传的通用模板。
升级到 MySQL 8.4 这件事,回头看起来,真正难的不是那几行升级命令,而是升级前后的细致观察和快速排障能力。如果你也正在准备升级,我给各位最真诚的建议就是:给足时间、做足演练、预留回滚,然后大胆地往前走。官方 Bug 别怕,它有补丁;EOL 焦虑也别怕,LTS 版本就在那里等着你。把这次踩坑记录下来,下一次升级,你的底气绝对不一样。