1. 升级前先想清楚:为什么升、风险在哪
前两天接了一个升级工单,把一台跑了近两年的MySQL从5.7升到8.0。接手前我以为这活儿不复杂——下载新版本、停库、替换二进制、启动,最多再跑一遍upgrade脚本,半天应该能搞定。真正落地才发现,MySQL升级这件事,难的不是“装新版”,而是升级过程里的兼容性迁移、数据安全和业务无感切换。特别是当你的业务系统里还跑着老存储过程、老驱动、老连接池配置的时候,每一步都可能变成坑。
这篇文章我把整个升级过程踩过的坑、列过的检查项、写过的脚本和最终验证方案都整理一遍,适合这几类人看:正在准备给线上MySQL做版本升级的DBA或运维同学、跑着MySQL 5.7想升8.0但迟迟不敢动手的全栈开发、以及所有要把“升级”当成一次正经项目来推进的同行。看完你至少能知道:升级前要准备什么、升级中每一步在做什么、升级后怎么确认业务真的没受影响。
先说结论:MySQL升级本质上是“一次有计划的数据搬迁加一次兼容性适配”。所谓搬迁,是因为数据库不只是几个文件,它上面挂着结构定义、权限体系、配置参数、插件、存储过程、事件调度器、binlog位点,这些都要跟着版本一起走;所谓适配,是因为新版本对老版本的行为约定可能完全不认账。MySQL官方文档里把大版本升级定义为一次“不可逆的数据库迁移事件”,这个定位非常准确——一旦跨版本升级,向下兼容是不保证的,回滚的成本远高于预防的成本。
1.1 升级不是“装新版”,而是兼容性迁移
MySQL 5.7和8.0之间的差异,远不止版本号上的一个数字。我挑几个升级后最容易爆雷的点,提前给你打个预防针:
- 认证插件从
mysql_native_password换成caching_sha2_password。老版本的客户端驱动、老版本的Navicat、老版本连接池,默认不认新插件,连上就报Authentication plugin 'caching_sha2_password' cannot be loaded。 - 系统表结构从MyISAM全部换成InnoDB,元数据的管理方式变了,以前能在
.frm文件里看到的表结构定义,在8.0里直接看不到了。 - 默认字符集排序规则从
utf8mb4_general_ci变成utf8mb4_0900_ai_ci,如果业务里对排序结果有依赖,这里要重新验证。 - SQL模式默认值变严格了,
ONLY_FULL_GROUP_BY、NO_ZERO_DATE、NO_ZERO_IN_DATE、STRICT_TRANS_TABLES默认开启。以前能跑的SQL,升级后可能直接报错。 - 隐式类型转换行为变了,数值和字符比较的规则更严格,可能出现原来走索引的SQL升级后索引失效。
- 8.0里一些老参数直接被移除,比如
query_cache_size、innodb_file_format、innodb_large_prefix,配置文件里还写着这些参数,MySQL可能直接启动失败。
这些差异决定了你的升级方案不能是“把文件拷过去就完事”,而是要先做一轮全量的兼容性体检,确认业务SQL、存储过程、配置项都在新版本里还能正常工作。
1.2 数据量越大,兼容性成本越高
我这次升级的库总共1.2TB,核心表最大的一张超过200GB,binlog一天能产生80GB。这种规模下,任何“跑一遍脚本就完事”的幻想都会被打碎。你需要注意三个数字:
- 备份耗时:物理备份全库用了将近4个小时;
- 升级耗时:mysqld启动后的数据字典升级跑了将近40分钟;
- 验证耗时:全库逻辑导出校验,跑了6个多小时。
所以升级窗口的估算,绝不能只算“停库几分钟”,而是要把备份、传输、升级、校验全链路的时间都算进去。很多团队升级翻车,就是低估了这个总时长,最后凌晨三点发现窗口不够,被迫带着半升级状态硬扛业务高峰。
1.3 升级路径怎么选:in-place还是逻辑迁移
两条主流路线,我直接给结论:
- 同机rpm替换(in-place升级):数据文件原地保留,安装新版本后启动时自动升级数据字典。耗时相对短,适合版本跨度不大、环境相对标准化的场景。5.7升8.0走这条路最常用。
- 逻辑迁移(mysqldump导出导入,或xtrabackup备份恢复到新实例):数据重新导入,天然解决了结构、数据、权限的兼容性问题,适合跨大版本、异构环境、或者顺便要换服务器换系统的场景。缺点是时间长,导入导出对业务影响大。
我用的是两者结合:先用xtrabackup做物理全备,把数据恢复到一台新服务器上,在新服务器上执行rpm升级验证,确认没问题后,再回头处理原机的升级。这样既有了“先验证后操作”的安全垫,又不至于被逻辑导入导出的时间拖垮。
2. 升级前必须做好的三件事
很多升级事故,问题出在准备工作做得不够。MySQL升级前有三件事,缺一件都别动手。
2.1 全套备份加恢复演练,不是“备份”而是“能恢复”
备份不是执行一条命令就完了,而是要确认备份文件能真正恢复出可用的数据。我这里说的“可用”,有三个层次:
- 备份文件本身完整,没有损坏;
- 恢复出的数据能启动,能查询;
- 恢复出的数据能和原库的binlog位点对上,能继续追增量。
mysqldump做逻辑备份时要注意参数,别只写一句mysqldump -uroot -p --all-databases > backup.sql就交差。我这边实际用的备份策略是双轨并行:
- 物理备份用xtrabackup做全备加增量,备份期间记录binlog文件名和位点;
- 逻辑备份用mysqldump,加
--single-transaction --quick --routines --events --triggers --hex-blob,确保存储过程、事件、触发器、二进制类型都完整导出。
恢复演练至少要做两次:一次在测试机上验证备份能恢复,一次在备用机上验证恢复出来的数据能被业务SQL正常查询。很多人跳过演练直接升,我劝你千万别学——备份不能恢复的时候,它只是一堆占用磁盘空间的无用数据。
2.2 兼容性体检:把检查项列成清单
升级前,我建议你把下面这些检查项整理成一张表,逐项确认:
| 检查项 | 检查方法 | 关注点 |
|---|---|---|
| 存储过程、函数、触发器、事件数量 | select count(*) from information_schema.routines;等 | 升级后这些对象是否全部保留 |
| 老SQL模式依赖 | select @@sql_mode;并对比业务代码中依赖默认值的SQL | 升级后默认值行为是否变化 |
| 保留字和字段名 | 检查建表语句、SQL中是否使用新版本保留字 | 8.0新增了部分保留字,可能导致SQL解析失败 |
| 字符集和排序规则 | select table_schema, default_character_set_name from information_schema.schemata; | 排序行为变化是否影响业务 |
| 失效索引 | select table_name, index_name from information_schema.statistics; | 隐式转换、索引失效问题 |
| 老参数引用 | 检查my.cnf中是否有8.0已移除的参数 | 配置文件报错导致启动失败 |
这一步不要省,尤其是业务系统如果经过了多年迭代,没人能拍胸脯保证所有SQL都规范。体检越细,升级后的惊吓越少。
2.3 升级窗口和回滚预案
升级窗口建议选业务低峰期,并且至少预留出“备份耗时+升级耗时+验证耗时”两倍的时间buffer。回滚预案要提前写好:保留旧版本的rpm安装包,upsream的数据目录完整副本留一份在原位或备份盘上。升级出问题的时候,能停掉新版本、恢复旧数据目录、重新启动老版本,这个操作必须提前演练一遍,而不是等到出问题再查文档。
3. Linux环境下rpm方式升级实操:从5.7到8.0
下面进入正题,这节内容是整个升级过程的核心,我按实际操作顺序展开。
3.1 环境整理和旧版本信息采集
升级前先摸清家底。这一步我在原机上执行了这些命令,把关键信息落到文本文件里保存:
# 确认当前版本 mysql -uroot -p -e "select version();" # 查看插件加载情况 mysql -uroot -p -e "show plugins;" # 查看当前生效的全部参数 mysql -uroot -p -e "show variables;" # 查看数据库、表、存储过程等对象清单 mysql -uroot -p -e " select table_schema, count(*) from information_schema.tables group by table_schema; select routine_schema, count(*) from information_schema.routines group by routine_schema; " # 确认配置文件路径和内容 mysql --help | grep -A 1 'Default options' cat /etc/my.cnf这一步容易踩的坑是:my.cnf里有大量历史遗留参数,有些参数你可能已经忘了为什么加进去。升级到8.0后,参数名变化、参数被移除的情况很常见。我这次就遇到query_cache_type和query_cache_size直接导致mysqld无法启动——8.0把查询缓存整个移除了,配置里残留的这两个参数必须删掉。
建议升级前用这个命令做一次参数校验,逐条确认哪些参数在新版本里还存在,哪些已经被改名或移除:
mysqld --verbose --help | grep -E 'query_cache|innodb_file_format|innodb_large_prefix'如果没有任何输出,说明这些参数在新版本里已经不存在了,必须从配置文件里清掉。
3.2 关停业务前最后的数据确认
升级窗口正式开始时,先不要急着重启数据库。按这个顺序来:
- 通知业务侧进入只读维护状态(对外页面按流程挂维护通知,让用户知道这段时间系统在升级)。
- 在MySQL里执行
FLUSH TABLES WITH READ LOCK;,把内存里的脏页强制刷盘,保证数据文件处于一致状态。 - 执行
SHOW MASTER STATUS;,记录当前binlog文件名和Position。 - 再做一次全量物理备份,确认备份完成后才解除锁。
- 执行
SHOW MASTER STATUS;再次确认位点没有变化。 - 用
/etc/init.d/mysql stop或systemctl stop mysqld关停数据库。
这里我要特别强调第4步:很多人以为备份在升级前一晚做过了,升级窗口开始时就不用再做。但备份和升级窗口之间,业务还在写入,binlog还在增长。你升级用的“基线数据”,必须是最新、最一致的那一份。所以窗口内再补一次全备,不是多余,是保险。
3.3 安装新版本rpm包
我这次是在CentOS 7环境上操作,用的是官方Yum仓库的方式。大致流程如下:
先确认系统里已安装的MySQL相关包,避免和旧版本冲突:
rpm -qa | grep -i mysql然后下载并安装官方仓库配置包。这里要注意,5.7和8.0的仓库配置包是区分开的,直接装对应的release包即可:
# 下载mysql 8.0的仓库配置包 wget https://repo.mysql.com/mysql80-community-release-el7-7.noarch.rpm # 安装仓库配置 rpm -ivh mysql80-community-release-el7-7.noarch.rpm # 安装8.0社区版服务端 yum install mysql-community-server -y安装过程中yum会自动处理依赖,但要注意几点:
- 如果之前安装的是5.7版本,建议先移除5.7的server包,但保留数据目录。数据目录一般在
/var/lib/mysql,不要动它。 - 如果直接yum update,可能会把5.7自动替换成8.0,这种情况下更要谨慎,确认数据目录没有被初始化流程覆盖。
- 安装完成先不要急着启动服务。先看看数据目录里的文件是否完整,特别是
ibdata1、ib_logfile0、mysql目录这些关键文件。
这里有个容易被忽略的细节:5.7升级到8.0时,mysqld第一次启动会执行数据字典的自动升级。这个阶段mysqld的日志里会输出大量[Note] InnoDB: Upgrading...之类的内容,耗时和你库的大小直接相关。我这次1.2TB的库,这个阶段跑了将近40分钟。期间绝对不能强制kill进程、绝对不能重启机器,否则数据字典会处于半升级状态,后续基本没法救。
启动前检查一下配置文件,把8.0里已经移除的参数注释掉。我整理的配置文件改动清单:
[mysqld] # 8.0已移除,必须删除 # query_cache_type=0 # query_cache_size=0 # 8.0建议显式配置 character-set-server=utf8mb4 collation-server=utf8mb4_0900_ai_ci # 老客户端兼容,视情况开启 # default-authentication-plugin=mysql_native_password启动命令:
systemctl start mysqld启动后立刻查看错误日志:
tail -f /var/log/mysqld.log如果你走到这一步,日志里没有出现[ERROR]级别的错误,说明数据字典升级顺利。
3.4 升级后的数据校验和存储过程专项检查
mysqld第一次启动会自动完成数据字典升级,但“启动成功”不等于“数据没问题”。我升级完第一件事是执行全库校验,确认引擎层数据文件没问题:
mysqlcheck -uroot -p --all-databases --check-upgrade这个命令会把所有表都检查一遍,发现不一致会直接报错。同时还要验证几类容易被忽略的对象:
- 存储过程和函数:升级后语法解析规则可能变化,以前能创建的存储过程,可能因为新版本保留了字或者SQL模式变化而无法执行。我建议逐个库调用
SHOW PROCEDURE STATUS;确认对象存在,再抽样执行几个核心存储过程,确认返回结果和升级前一致。 - 事件调度器:确认
EVENT还在,且event_scheduler参数状态正确。 - 触发器:用
SHOW TRIGGERS;确认都在。
这个过程里我踩了一个比较典型的坑:业务里有个存储过程依赖NO_ZERO_DATE这个非严格模式。5.7的默认sql_mode是ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,8.0把NO_AUTO_CREATE_USER移除了,但严格控制行为整体是趋严的。那个存储过程在8.0默认模式下跑直接报错,后来确认是插入语句里用了'0000-00-00'这种零日期,需要在会话级别把NO_ZERO_DATE加回去才能兼容。这个问题如果提前没有业务sql_list的review,升级后再排查就会很被动。
建议升级窗口内预留一段时间,让业务侧配合跑一遍核心接口的回归测试,尤其是涉及写入、日期处理、聚合查询的接口。
4. 升级后的配置调优与常见报错排查
升级完成、数据校验通过,不意味着可以松口气。8.0的默认配置和5.7不太一样,很多参数如果不主动调优,性能可能反而不如升级前。同时,客户端连接层的问题也会在升级后集中爆发。
4.1 升级完成后第一时间要调整的配置
- 内存参数:8.0默认的
innodb_buffer_pool_size是128MB,这显然不是给生产库用的。我按物理内存的60%~70%来设置,比如机器128GB内存,设成80GB左右起步,再根据命中率微调。 - binlog策略:8.0默认开启binlog,
expire_logs_days参数被改名为binlog_expire_logs_seconds。旧配置里写着expire_logs_days=7的,升级后实际不会生效,必须改成binlog_expire_logs_seconds=604800。 - 连接数:确认
max_connections是否还够用,8.0的线程模型有些变化,连接数设置过大会占用额外内存。 - 慢查询:
slow_query_log=ON、long_query_time=2这类参数保持和升级前一致,方便对比升级后的性能变化。 - undo表空间:8.0的undo是自动管理的,不再需要手动配置
innodb_undo_tablespaces这类参数。
4.2 客户端连接层:驱动、SSL、字符集
升级后连接层报错,是另一个高频翻车现场。最常见的有三种:
第一种是驱动版本太老。5.7时期的JDBC驱动、PHP mysqli扩展、Python mysqlclient,默认不认8.0的caching_sha2_password认证插件,连接时报Authentication plugin 'caching_sha2_password' cannot be loaded。解决方式有两个:升级客户端驱动到支持8.0的版本;或者如果业务侧暂时没法升级驱动,在MySQL侧做兼容处理,把默认认证插件改回老插件。但我不建议长期走这条路,8.0.28之后MySQL已经把这个参数改名并且逐渐弱化,老插件的支持力度只会越来越低,迟早要升级驱动。
第二种是Navicat等图形化工具连不上8.0。老版本Navicat默认不支持8.0的认证方式,升级到支持MySQL 8.0的新版客户端即可解决。这里顺便说一句:工具连接不上,优先考虑官方客户端升级,别在旧工具的“破解补丁”上浪费时间,那些方案既不安全,也解决不了协议层面的问题。
第三种是SSL连接错误。8.0默认开启SSL,客户端如果走非本地TCP连接,服务端会要求安全的传输通道,或者至少完成公钥交换。老驱动如果没有配置SSL或者不支持RSA公钥交换,客户端会报Unable to connect to any of the specified MySQL hosts这类错误。排查思路是:确认驱动支持新认证;确认连接串里有没有配置SSL相关的参数;如果业务环境是内网低风险,可以在MySQL侧关闭强制SSL,设置require_secure_transport=OFF,让老驱动先用非加密通道连接起来,再逐步过渡。
字符集方面,8.0默认utf8mb4_0900_ai_ci,如果你的连接串没有显式指定characterEncoding=utf8mb4,老应用可能出现乱码或者排序不一致。这个比较容易排查,但难在可能影响到的业务点位很分散,建议升级前就用连接串统一规范。
4.3 高频报错排查表
把升级过程中最容易遇见的报错整理成一个速查表,方便你直接对照:
| 报错现象 | 可能原因 | 处理方式 |
|---|---|---|
Authentication plugin 'caching_sha2_password' cannot be loaded | 客户端驱动不支持8.0认证插件 | 升级驱动,或临时把用户改回mysql_native_password |
Unknown system variable 'query_cache_size' | 配置文件残留8.0已移除参数 | 删除配置中query_cache_type、query_cache_size |
Access denied for user 'xx'@'xx' | 升级后权限表变了 | 确认连接用户是否在8.0中存在,必要时重建权限 |
Lost connection to MySQL server during query | max_allowed_packet设置过小 | 调大max_allowed_packet,并同步确认客户端连接参数 |
InnoDB: Table 'xxx' doesn't exist in engine | 表元数据不一致 | 用mysqlcheck --repair修复或从备份恢复该表 |
启动报错缺少libcrypto.so.1.1等动态库 | 系统环境依赖和MySQL新版本不匹配 | 检查openssl、glibc版本,按官方要求安装依赖 |
SQL执行报错Expression #N of SELECT list is not in GROUP BY clause | 8.0默认开启ONLY_FULL_GROUP_BY | 修改SQL或调整会话级sql_mode |
这里特别说一下“升级了某个系统组件,但程序还是用的旧版本”这个问题。升级MySQL的时候如果遇到依赖库版本不对,很多同学第一反应是去升级openssl、升级glibc、升级gcc,但升级完发现ldd mysqld依然显示旧版本路径。这通常不是升级没成功,而是动态链接器缓存没有刷新,或者程序链接了绝对路径下的旧库。排查思路:
ldd /usr/sbin/mysqld | grep -E 'ssl|crypto'如果发现指向的是旧路径,可以检查LD_LIBRARY_PATH环境变量、/etc/ld.so.conf里的配置,执行ldconfig刷新缓存后再试。系统组件升级这个事情,优先级一定要排在MySQL升级之后——先把MySQL升上去,如果确实缺依赖再针对性补,别本末倒置,否则可能把系统搞到不可用的状态。
5. 升级过程中的心得和小技巧
整个流程走下来,我自己最大的体会是:MySQL升级不是一个“技术动作”,而是一个“项目”。技术动作只是其中一部分,更关键的是把升级窗口、业务影响、回滚方案、验收标准全部拉齐,让各个环节的人都清楚自己在什么时间点做什么。
有几个小技巧,常规文档里不会写,但对实操帮助很大:
- 升级前把
my.cnf完整保存一份带时间戳的副本,升级后每次启动出问题,先对比新旧配置,节省大量排查时间。 - 8.0第一次启动会自动升级数据字典,但升级进度不会实时输出到终端,而是在错误日志里。升级期间建议单独开一个终端持续
tail -f /var/log/mysqld.log,看到[Note] InnoDB: Upgrade of the data dictionary completed类似内容,才说明这个阶段结束了。别着急执行下一步。 - 备份文件的校验不能只看文件大小,要真正去恢复一遍。我这次升级就靠恢复演练发现了一个备份遗漏:有台实例的
EVENT没有出现在逻辑备份里,原因是最早的备份脚本漏了--events参数。
再分享一个回滚的小经验:升级完成后,保留旧版本的rpm包和原数据目录,不要急着清理。我之前有次升级,因为业务验证拖了比较久,顺手把旧包删了。后来真出了问题想回滚,发现旧版本根本装不回来,只能硬着头皮在原版本上修。后来我就养成了习惯:升级成功后至少保留旧包两周,确认新版本稳定运行后再清理。
最后补充一点:如果业务对可用性要求极高,常规的在位升级方式可能不太适合你。更稳妥的路子是搭一套独立的8.0新实例,做主从同步把数据追平,然后做一次主从切换。这个方案对业务的影响最小,但架构成本和运维复杂度会高不少。我个人观点是,先根据自己业务的容忍度选好升级方式,再按这篇文章的流程走,至少能避免绝大部分升级事故。