我上周刚帮朋友把一套跑了三年的MySQL 5.7实例升到了8.0,整个过程比预想的要顺,但中间也踩了软件源、认证插件、sql_mode几个坑。MySQL 8.0发布好几年,社区版和企业版都已经非常稳定,5.7官方维护也进入了末期,从安全补丁和长期维护角度看,升级这件事宜早不宜迟。
这篇我把自己完整跑过的升级路径、检查脚本、备份方案、踩坑记录都整理出来,覆盖Linux和Windows两种常见环境下的操作差异,适合手里管着5.7实例、准备往8.0迁的DBA、后端开发和运维同学参考。
1. 升级前必做:版本差距与风险评估
1.1 为什么5.7升8.0不是小事
很多同学觉得MySQL升级不就是把安装包换一下、把数据目录指向新版本吗?在5.6升5.7的年代,这种想法还算可行,但从5.7升8.0完全不是同一个概念。
8.0是一次大规模重构的版本,底层的数据字典从MyISAM表换成了InnoDB,原来的frm文件机制彻底移除,权限表结构也改了。这意味着MySQL 8.0首次启动时会自动对数据字典做一次重建和升级,这个过程不可逆,而且一旦开始就不能用旧版binary再去启动旧数据目录。
另一个容易忽略的问题是行为变更。8.0默认字符集是utf8mb4,5.7默认还是utf8mb3;默认认证插件换成了caching_sha2_password,老客户端驱动可能连不上;sql_mode默认值多了STRICT_TRANS_TABLES、NO_ZERO_IN_DATE、NO_ZERO_DATE、ONLY_FULL_GROUP_BY等一堆严格模式。这些变化不会在升级时报错,但会在升级后某个不常见的SQL上突然炸出来。
注意:8.0移除了查询缓存(Query Cache)相关参数。如果配置文件里还有
query_cache_type或query_cache_size,MySQL 8.0会直接启动失败。
所以在动手之前,先要搞清楚一件事:你的业务SQL和连接驱动,是否能接受这些行为差异。数据库升级的表面工作是换版本,真正的难点是在升级后让应用无感知。
1.2 用官方检查脚本提前扫雷
在正式升级之前,先用MySQL官方提供的升级检查工具把兼容性问题扫一遍,能省掉后面80%的麻烦。
MySQL 8.0安装包里自带一个mysqlcheck命令,大众用法是检查表,但它还有一个隐藏能力——升级预检查。MySQL官方推荐的标准做法如下:
# 先连接到旧版本5.7实例 mysql -uroot -p # 在MySQL 5.7里执行升级检查SQL mysql> USE mysql; mysql> CHECK TABLE user, db, tables_priv, columns_priv, procs_priv, proxies_priv, roles_mapping FOR UPGRADE;对5.7版本,更完整的做法是用mysql_upgrade --check命令,它只检查,不实际执行升级。但要注意mysql_upgrade这个工具在8.0里已经被废弃了,8.0的自动升级机制取代了它。
如果你已经下载了MySQL Shell(8.0官方推荐工具),那直接用它的util.checkForServerUpgrade()方法最省心:
-- 在MySQL Shell里执行 mysql-js> util.checkForServerUpgrade('root@localhost:3306', {"password": "your_password"})这个命令会逐项检查几十个兼容性问题,输出结果类似这样:
Checking for version upgrade ... 3) Incompatible remote tables found No issues found. ... 7) Columns with implicit defaults No issues found. ... Errors: 0 Warnings: 12 Notices: 0输出里的Warnings不一定都是致命的,但每一条都值得看一遍。比如它会警告某些表使用了utf8mb3字符集、某些字段用了废弃的zerofill属性、某个存储过程带有DEFINER用户等。这些警告就是升级后可能出现问题的清单,提前截图保存。
我自己实测的经验是,在数据量不大(几十GB以内)的情况下,这个检查只要几分钟。如果实例有几百GB甚至上TB的数据,建议在业务低峰期做,因为检查会扫描所有表的元数据,对IO有一定压力。
2. 备份与升级方案选型
2.1 无论走哪条路,先做一份可靠备份
版本升级最忌讳的事情就是不备份直接上。哪怕官方文档说了5.7到8.0的升级路径是支持的,你也必须假设升级过程中可能出现意外——比如磁盘满了、断电、InnoDB数据字典重建失败等。
备份方案有两种,可以都做:
第一种是用mysqldump做逻辑备份。优点是简单、通用、可靠,缺点是慢,大库恢复时间长。命令参考如下:
mysqldump -uroot -p --single-transaction --routines --triggers --events --set-gtid-purged=OFF --all-databases > full_backup_2025.sql参数解释:
--single-transaction:InnoDB表用一致性快照,不锁表,对线上业务影响最小。--routines、--triggers、--events:把存储过程、触发器、定时事件都导出来,漏一个后面都麻烦。--set-gtid-purged=OFF:避免导出文件带上GTID信息,方便后续导入到新实例。
第二种是用xtrabackup做物理备份。优点是速度快,支持增量,适合大库场景。恢复方式是把备份文件解压到新数据目录,然后启动MySQL自动恢复。
提示:5.7和8.0的xtrabackup版本不通用。备份5.7必须用Percona XtraBackup 2.4,备份8.0要用8.0.x版本,千万别混用。
我在实际升级前,会把这两种备份都做一遍。物理备份放本机,逻辑备份再额外拷贝一份到其他机器。万一升级真的搞砸了,还能迅速回滚到旧版本。
2.2 两条主路径:原地升级与逻辑迁移怎么选
5.7升8.0有两条主流路径,各有优缺点,需要根据你的停机窗口和实例规模来选。
路径一:原地升级(In-Place Upgrade)
操作逻辑很简单:停掉5.7,把8.0的二进制替换进去,启动8.0让它自动升级数据字典。这个方式的优点是快,不需要导数据,几十GB的库可能只用停机几分钟,剩余时间都在做内部元数据升级。缺点是在升级过程中如果遇到问题,回滚很麻烦,因为数据字典已经变了,旧版本无法识别。
适用场景:停机窗口短、数据量大、你有充分的备份和回滚预案。
路径二:逻辑迁移(Logical Migration)
先用mysqldump或者MySQL Shell的util.dumpInstance把数据逻辑导出,然后安装全新的8.0实例,再把数据导入。这种方式最稳,等于重新搭一套环境,导入前可以顺手清理一下冗余数据、调整表结构,还能顺便测一遍新环境。缺点是慢,几百GB的库导出导入可能要跑几小时甚至一整天。
适用场景:数据量不大、停机时间充足、或者你想借升级的机会整理一遍库表结构。
在我这次的实际操作中,线上那套库大概200GB,我衡量了一下,选择了原地升级。下面两个章节会把两条路径分别讲清楚,你可以根据自己的情况选一条。
3. 实战一:最省事的原地升级(In-Place)
3.1 环境信息与准备动作
先列一下我这次操作的环境:
- 操作系统:CentOS 7.9(Linux环境)和一台Windows Server 2012 R2做对照测试
- 旧版本:MySQL 5.7.44,数据目录在
/var/lib/mysql - 新版本:MySQL 8.0.40(LTS版本,官方长期支持)
- 数据量:约200GB,主要业务表都是InnoDB
停机前的准备动作如下:
第一步,把配置文件里的废弃参数清掉。5.7配置里常见的这几个参数在8.0里已经不存在或者默认值变了,必须先处理:
# 需要删除或注释掉的8.0不再支持的参数 # query_cache_type = 1 # query_cache_size = 67108864 # innodb_large_prefix = ON # innodb_file_format = Barracuda # innodb_file_format_check = ON # innodb_file_format_max = Barracuda # innodb_purge_batch_size # max_worker_threads 调整为系统自动第二步,检查sql_mode。5.7时代很多开发习惯设置一个宽松的sql_mode,比如NO_ENGINE_SUBSTITUTION。8.0默认模式很严格,建议升级前先在旧实例上把sql_mode调成8.0默认值,跑几天观察业务有没有异常。
8.0默认的sql_mode是:
sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION升级前把5.7实例的sql_mode临时改成这个值,观察一到两天。如果应用出现报错,至少能在升级前发现并修掉,而不是在升级后发现。
第三步,确认磁盘空间。MySQL 8.0升级过程中需要重建数据字典,临时文件和undo日志会膨胀,建议预留至少数据目录当前大小的1.5倍可用空间。我那次200GB的数据,磁盘剩余空间有300GB多,跑起来才不慌。
3.2 替换二进制并启动升级
环境准备好的前提下,原地升级的操作用时大概只有二十分钟。
首先是干净地停掉旧实例。注意不要直接kill -9,MySQL 8.0的升级对数据文件的一致性要求很高:
systemctl stop mysqld # 或者 service mysql stop停掉之后确认进程已经退出,再用ps或者ss确认端口3306已经释放。
然后替换二进制文件。如果你是yum或apt安装的,直接用官方仓库切换版本:
# 先备份旧配置 cp /etc/my.cnf /etc/my.cnf.bak_57 # 卸载5.7(注意不要删掉/var/lib/mysql) yum remove mysql-community-server # 安装8.0 yum install mysql-community-server如果用tar包方式安装,先把旧的/usr/local/mysql重命名备份,再解压8.0的tar包到原路径,确保my.cnf里basedir和datadir路径不变。
mv /usr/local/mysql /usr/local/mysql_57_bak tar -xzf mysql-8.0.40-el7-x86_64.tar.gz -C /usr/local/ mv /usr/local/mysql-8.0.40-el7-x86_64 /usr/local/mysql chown -R mysql:mysql /usr/local/mysql接下来就是关键一步:启动8.0实例,让它自动执行升级。
systemctl start mysqld首次启动时,MySQL会自动检测到旧版本数据目录,然后开始数据字典升级。日志位置在/var/log/mysqld.log,这个过程类似:
[System] [MY-013415] Server socket created on IP: '0.0.0.0:3306' [System] [MY-010116] Server started in daemon mode [System] [MY-013380] Upgrading the data directory. [System] [MY-013381] Data directory upgraded successfully.看到这几行基本就成功了。8.0的升级日志比5.6/5.7时代的mysql_upgrade输出简洁很多,没有逐表刷新的过程,因为8.0把元数据升级做得更快了。
注意:8.0首次启动后,不要再把数据目录指回5.7的二进制启动,否则可能损坏数据字典。升级成功后,旧的
mysql_57_bak目录可以暂时保留在磁盘上,确认稳定运行一周后再删。
Windows环境的操作逻辑完全一致,只是安装方式不同。下载8.0的zip压缩版,解压后把bin目录加入PATH,然后用管理员权限打开CMD,用mysqld --initialize-insecure初始化新实例(如果新数据目录),再net start mysql启动。如果升级已有数据目录,直接启动后同样会自动升级。
4. 实战二:逻辑迁移升级(mysqldump / MySQL Shell)
4.1 导出阶段要注意的参数
逻辑迁移适合想借升级机会搭一套全新环境的场景。我先说导出,再讲导入,最后说校验。
导出阶段我推荐直接上MySQL Shell的util.dumpInstance,它比mysqldump快很多,而且是8.0官方推荐的逻辑备份工具,开启并行后能把多张表的导出任务同时跑起来。
mysqlsh root@localhost:3306 --util dump-instance /backup/mysql_dump --ocn参数--ocn是--consistent的简写,保证一致性快照。不指定的话,导出期间新写入的数据可能导致备份集不一致。
如果你还是习惯用mysqldump,输出要加--routines --triggers --events这几个选项,前面提过。另外5.7的mysqldump导出的文件,在8.0导入时基本都能兼容,因为官方在这条升级路径上做了大量兼容工作。
导出完成后,确认一下备份文件大小和库里实际数据量是否对得上。我见过有人导出到一半磁盘满了还没发现,结果导入的时候才发现备份文件是残缺的。
4.2 导入阶段与数据校验
导入前,新8.0实例要做的事情和原地升级的准备工作一样:确认sql_mode、清掉废弃参数、规划好字符集。特别是字符集,导入前就把character_set_server设成utf8mb4,别等导完再改,否则可能有乱码风险。
mysql -uroot -p < /backup/mysql_dump/dump.sql如果用的是MySQL Shell导出的目录备份,导入用:
mysqlsh root@localhost:3306 --util load-dump /backup/mysql_dump导入过程可能会报字符集相关的warning,不用慌,先全部导入完,再统一检查。
导入完成后,数据校验这步千万别偷懒。我用的是最直接的办法——两套实例同时跑统计SQL对比:
-- 旧实例 SELECT table_schema, COUNT(*) FROM information_schema.tables GROUP BY table_schema; -- 新实例 SELECT table_schema, COUNT(*) FROM information_schema.tables GROUP BY table_schema;然后把几个核心大表的行数、max(id)、sum(某个关键字段)都拉出来对比一遍。如果之前开了GTID,还可以用GTID集合来验证数据是否完整。5.7和8.0的GTID格式是一样的,基本可以直接对照。
在实践里,逻辑迁移是最稳妥的方式,因为导入之前可以检查所有对象的定义,有问题早发现早修复。缺点就是慢。如果你的库在500GB以上,我不建议用mysqldump逻辑迁移,直接用原地升级更快。
5. 升级后的验证、参数调整与常见报错
5.1 升级完成后的系统健康检查
升级完成不代表事情结束,真正的工作——验证和适配——才刚开始。我总结了一套升级后的体检清单,按顺序做完,基本能确认实例状态正常:
第一,确认版本和状态:
SELECT VERSION(); -- 期望输出 8.0.x SHOW STATUS LIKE 'uptime'; SHOW VARIABLES LIKE 'innodb_version';第二,检查所有库表是否正常,重点关注是否有表被标记为in use或corrupted。8.0里可以用:
CHECK TABLE your_table_name;第三,验证账号和权限。8.0升级后权限表结构变了,mysql.user里新增了多个字段,比如authentication_policy相关的列。如果升级前用的是mysql_native_password插件,升级后连接可能报错。当前主流客户端(如最新的Navicat、MySQL Workbench、各种语言的驱动)都已经支持caching_sha2_password,但老版本驱动的确会有问题:
-- 查看现有账号的认证插件 SELECT user, host, plugin FROM mysql.user; -- 如果有需要兼容的旧客户端,可以改回native_password ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码';但我强烈不建议把新版本默认的caching_sha2_password全部改回mysql_native_password,因为那意味着你放弃了8.0安全性升级的核心优势。正确做法是升级客户端驱动,而不是降低数据库的安全标准。
第四,确认计划任务、存储过程和触发器状态。8.0升级后,存储过程的DEFINER如果指向已删除用户,调用时会报错。检查一下information_schema.routines里的DEFINER字段,确保对应的账号都存在。
5.2 新版本适配调整:认证插件、sql_mode、缓存参数
升级后,有几个参数和配置需要根据业务实际调整。
第一个是sql_mode。虽然升级前让你提前改成8.0默认模式跑几天,但部分老业务可能仍然不兼容。比如ONLY_FULL_GROUP_BY模式下,SELECT a, COUNT(*) FROM t GROUP BY b这种写法会直接报错。遇到这种问题,在升级初期可以临时把ONLY_FULL_GROUP_BY去掉,但心里要明白这只是过渡方案,长期必须改SQL。
# my.cnf 临时兼容 sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION第二个是内存参数。8.0对缓冲池的利用效率比5.7高,可以适当调大innodb_buffer_pool_size。如果服务器内存足够,把它设置到物理内存的60%-70%都没问题。但注意innodb_buffer_pool_instances在8.0里默认值随buffer pool大小自动调整,不再需要手动指定。
另一个容易忽略的参数是innodb_redo_log_capacity。8.0里替代了5.7的innodb_log_file_size,默认是100MB,但对写入量大的业务来说可以调大。我一般设置成1G或2G,减少日志文件切换频繁带来的性能抖动。
innodb_redo_log_capacity = 1G第三个是字符集。8.0默认utf8mb4,如果你的库表和连接还停留在utf8mb3或latin1,建议在升级后的稳定期逐步迁移到utf8mb4,尤其是存表情符号或者其他多字节字符的场景。
注意:从
utf8mb3迁移到utf8mb4会涉及表重建,大表执行ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4时会有DDL锁,建议用pt-online-schema-change这类工具在低峰期做。
5.3 常见报错与排查速查表
升级后最常碰到的报错,我整理成了一个速查表,按报错信息、原因、解决办法三列排,实操中直接搜报错就能定位:
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
Unknown system variable 'query_cache_type' | 配置文件残留5.7参数 | 注释掉query_cache_type和query_cache_size |
Authentication plugin 'caching_sha2_password' cannot be loaded | 客户端驱动版本太老 | 升级驱动,或临时改账号认证插件为mysql_native_password |
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause | ONLY_FULL_GROUP_BY启用的严格检查 | 修改SQL或临时调整sql_mode |
Column count of mysql.user is wrong. Expected X, found Y | 权限表未正确升级 | 确保首次用8.0启动时完整执行升级流程,不要中途kill进程 |
Table 'mysql.proc' doesn't exist | 存储过程元数据迁移问题 | 用mysql_upgrade重新升级存储过程,或重建对应存储过程 |
[ERROR] InnoDB: Table 'xxx' in data dictionary has mismatch | 数据字典和表文件不一致 | 用ALTER TABLE xxx FORCE重建表,必要时用备份恢复 |
ERROR 1064 (42000): You have an error in your SQL syntax且提示rank、groups等词 | 8.0新增保留字 | 给字段名加反引号,或修改字段名 |
这里面有个非常容易踩的坑:8.0新增了一批保留字,比如rank、dense_rank、percent_rank、cume_dist、groups、json_table等,这些是窗口函数和JSON函数带来的。如果5.7的库表里恰好有叫rank的字段,升级后普通SELECT都能跑,但一执行某些涉及特定语法的语句就会语法错乱。排查起来非常头疼。所以升级前的兼容性检查一定要认真看输出,里面会列出这类字段。
还有个小问题是关于TIMESTAMP列的。8.0里对TIMESTAMP的默认值处理更严格,如果之前建表用了TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,升级后一般没问题。但如果用过DEFAULT '0000-00-00 00:00:00'这种值,在严格模式开启时会报错。解决办法是对这列做ALTER TABLE修改默认值,或者在连接会话里临时关闭NO_ZERO_DATE。
6. 最后再分享一点个人体会
这次升级让我最深刻的感受是:MySQL 8.0的升级,难点不在“升级”这个动作本身,而在升级前的评估和升级后的适配。
原地的二进制替换只要二十分钟,但为了让这二十分钟不出岔子,我花了将近两天做兼容性检查、参数调整、备份和演练。尤其是sql_mode的调整,我提前两周就在测试环境跑了一遍生产的所有核心SQL,抓到两个因为ONLY_FULL_GROUP_BY导致的慢查询,提前让开发重写了。
另外一个小建议是,升级完成后至少观察一到两周再清理旧版本的备份目录。期间如果出现任何疑难问题,至少还有一条回滚的路可以走。虽然8.0的数据字典升级后理论上是不能回滚的,但如果你在升级后短时间内发现重大问题,用逻辑备份重建5.7实例,再恢复数据,还是来得及的。
MySQL 8.0性能确实比5.7强很多,尤其是并发写入和复杂查询场景,我这边升级后有几个之前要几百毫秒的统计SQL,现在几十毫秒就跑完了。升级这件事,值得做,而且值得认真做。