
前几天帮客户做MySQL 5.7到8.0的升级核心库200多张表最大的表过亿行之前一直拖着不敢动。MySQL 8.0发布到现在已经好几年了5.7在2023年10月就正式结束生命周期不再有常规安全补丁还在生产环境跑5.7的团队升级8.0真的只是时间问题。这篇分享我把自己近期一次完整升级的经验整理出来从兼容性评估、升级路径选型、原地升级实操到最容易翻车的几个场景和完整的排查链路以及回退预案一次讲透。适合正在规划升级、或者已经卡在升级过程中的DBA、后端开发和运维同学参考。1. 升级前必须摸清的家底5.7与8.0的兼容性雷区升级8.0最大的风险不是版本切换本身而是那些从5.7时代继承下来的隐性习惯。很多库在5.7下跑得好好的SQL、连接串、配置项到了8.0可能直接罢工。下面这几类雷区是我每次升级前都会逐一排查的。1.1 认证插件变更老客户端集体掉线的隐患MySQL 8.0把默认认证插件从mysql_native_password换成了caching_sha2_password。这个改动在安全上是进步但代价是大量旧版客户端不认新插件。最常见的报错是Authentication plugin caching_sha2_password cannot be loaded或者建立连接时直接报Access denied for user。升级前必须先把用户表扫一遍确认哪些账号当前用的什么插件SELECT user, host, plugin FROM mysql.user;如果大量账号还是mysql_native_password就要有个心理准备升级后这些账号对应的业务连接大概率会断。我的处理策略分两步走短期兼容升级后临时把关键业务账号改回mysql_native_password保证业务不停长期收口在文档或工单系统里记录每个账号对应的客户端和驱动版本排期统一升级最终全部迁到caching_sha2_password。改回旧插件的命令在8.0里是这样ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY your_password;这里特别提一句升级后新创建的用户默认都是caching_sha2_password如果开发同学直接在线上库建号给老应用用不用等升级建完就报连不上。建议把这条经验同步给团队。1.2 默认字符集变化乱码不一定发生在表而发生在连接层8.0的默认字符集是utf8mb4默认排序规则是utf8mb4_0900_ai_ci。但升级不会自动把存量表的字符集改掉于是容易出现一种非常隐蔽的乱码服务端和客户端的character_set_server还是5.7时代的默认值很多老库其实是latin1业务写入的数据在8.0默认utf8mb4的连接上读出来中文直接变成问号。升级前建议扫描一遍所有表SELECT table_schema, table_name, table_collation FROM information_schema.tables WHERE table_schema NOT IN (mysql, information_schema, performance_schema, sys) AND table_collation NOT LIKE utf8mb4%;这次扫描的目的不是一定要把所有表都转成utf8mb4而是搞清楚哪些库表还是老字符集。如果业务一直用latin1存中文而且数据已经写入那么升级后重点检查连接参数让连接字符集和表字符集保持一致而不是盲目执行ALTER TABLE CONVERT TO CHARACTER SET utf8mb4——这个操作会锁表大表执行起来风险很高。1.3 SQL模式变化ONLY_FULL_GROUP_BY引发的合法SQL变非法8.0默认的sql_mode保留了ONLY_FULL_GROUP_BY和STRICT_TRANS_TABLES但移除了NO_AUTO_CREATE_USER同时加入了一些5.7里没有的组合。最典型的问题是5.7下能跑的GROUP BY查询在8.0下直接报错。SELECT a, b, COUNT(*) FROM t GROUP BY a;在ONLY_FULL_GROUP_BY开启时b没有出现在GROUP BY中也不是聚合函数列MySQL会直接拒绝执行。5.7下不少实例为了方便把ONLY_FULL_GROUP_BY从sql_mode里拿掉了所以老SQL能跑。升级到8.0之后如果沿用5.7里的非默认sql_mode有些老配置项已经不存在MySQL会启动失败如果用8.0默认值这类SQL就会开始报错。我的建议是不要直接关掉ONLY_FULL_GROUP_BY而是把涉及的业务SQL拉出来改掉。理由很简单关闭这个模式之后MySQL在GROUP BY时对非聚合列的行为是不确定的返回哪一行取决于执行计划轻者数据不准重者上线后引发脏数据。任何一个有责任感的DBA都不该用改全局配置的方式妥协。升级前可以用这个命令查看当前模式的完整值留好备份SHOW VARIABLES LIKE sql_mode;1.4 别靠肉眼用升级检查器做一次全面体检如果你以为靠上面这几项人工检查就能覆盖所有雷区那就太乐观了。MySQL官方提供了一个很实用的工具——MySQL Shell的升级检查器连上5.7实例后可以自动扫描不兼容项。这个工具非常值得在升级前跑一次而且应该是最先做的一步。mysqlsh --urirootlocalhost:3306 -- util.checkForServerUpgrade({targetVersion:8.0.36,outputFormat:JSON})检查结果会按ERROR、WARNING、NOTE三级分类。我遇到过的几种代表性结果级别常见提示含义ERRORSchema contains routers...存在已删除的系统表或对象依赖ERRORTables with obsolete temporal...表里存在旧版时间格式升级时自动转换会耗时WARNINGsql_mode contains deprecated...配置里的某些模式在8.0已被移除WARNINGInnoDB default_stats...统计信息相关参数建议调整NOTESeveral authentication plugins...老账号还在用旧认证插件提醒规划跑完分析之后把 ERROR 级别的项目逐条研究。绝大多数 ERROR 都需要在升级前处理掉而不是抱着先升上去再说的侥幸心理。这个工具同时也会给出每一条问题的解决建议虽然不是所有建议都直接可用至少能给你一个明确的排查方向。2. 两条平滑路线怎么选原地升级与主从滚动切换的取舍升级方案没有绝对的最优只有适不适合你的业务场景。我在实际项目中常用的是两条路线一条是原地升级in-place upgrade另外一条是主从滚动升级。两者的核心区别在于停机窗口和操作风险。2.1 原地升级简单直接但需要明确的维护窗口原地升级的思路不复杂停库替换MySQL二进制文件保持原数据目录不变启动8.0数据字典自动升级。这个方法的最大优点是不需要把数据导来导去尤其对大库来说省去了mysqldump导入导出的大量时间。缺点也同样明显从停库到8.0启动完成并验证可用整个过程中业务是中断的。数据量小、表结构简单的库几十秒到几分钟能完成如果是上百G甚至上T的库还要看是否涉及旧时间格式转换、系统字典升级等耗时可能远超预期。我用这种方法时有一个非常明确的准入条件业务允许至少30分钟的停机窗口且升级前能在测试环境完整跑一遍同样规模的升级演练。达不到这两个条件不建议直接上原地升级。2.2 主从滚动升级把感知降到最低如果业务对连续性要求高主从滚动升级是更好的选择。大体思路是利用MySQL原生的复制能力搭建一条5.7主库 → 8.0从库的复制链路等8.0从库追平主库日志后把业务连接切换到8.0从库上原5.7主库降级为待命或直接下线。这个过程对业务端的影响可以压缩到秒级甚至配合VIP漂移或数据库代理做到应用无感知。实现上的核心步骤大致如下5.7主库开启GTID模式如果之前没开需要先在线启用并重建复制关系部署8.0从库但不要初始化数据让复制自己把数据补齐建立复制关系时建议用MySQL Shell的clone插件或物理备份来快速拉齐初始数据集不要用mysqldump导几十个G的数据太慢了从库追平后在业务低峰做切换把读写流量指向8.0从库。这里顺带强调一句MySQL 8.0从库复制5.7主库是官方支持的路径前提是主库版本不能太低建议至少5.7.30以上。如果你们的主库还停留在5.7.20甚至更早先把主库本身的小版本升一升再谈跨大版本复制。2.3 我的选型逻辑很多团队一上来就问哪个方案最好我的习惯是先回答三个问题业务能承受多久的数据库中断如果超过10分钟会被投诉到上热搜那基本告别原地升级团队对复制、GTID、VIP漂移这些运维技能的熟练度如何不熟的话原地升级反而更可控数据库总量多大超过500Gmysqldump方案基本可以直接排除物理备份或clone是更现实的基础。如果是我自己维护的核心库我偏爱先升从库、再主从切换的滚动方式。理由很朴素它天然附带一个可回退的低版本主库。升级过程中遇到不可逆的问题把流量切回5.7主库就行而原地升级一旦8.0实例启动后写入过数据字典想降级就非常麻烦了。3. 原地升级全流程实操评估、切换、启动一个都不能省讲完路线选择我把最近一次原地升级的完整操作过程写下来这是Linux环境下以tar包二进制方式部署的常规场景。当然不同的系统包管理方式yum/apt会有细微差别但核心思路一致。3.1 升级前的目录与配置检查很多人拿到新二进制就直接替换结果启动时报一堆参数错误。我要说的第一件事在替换二进制之前先把配置文件和磁盘空间这两件事处理好。磁盘空间方面8.0升级过程中数据字典、redo log、undo表空间都会发生变化建议保留至少等于当前数据目录20%的额外空间。实际操作中我一般直接要求预留30%以上因为日志和临时文件很容易被低估。配置文件方面重点检查下面这些在8.0中已废弃或行为变化的参数query_cache_type、query_cache_size查询缓存已被移除配置里带着必挂innodb_file_format、innodb_file_format_checkInnoDB新版本不再支持innodb_large_prefix8.0中默认为ON且参数被移除old_passwords5.6时代遗留8.0直接不支持key_buffer_size对InnoDB无效如果只跑InnoDB可以移除innodb_buffer_pool_size参数本身有效但建议根据新版本内存管理重新评估。可以用下面这条命令快速找出配置文件里可能存在的问题grep -nE query_cache|innodb_file_format|innodb_large_prefix|old_passwords /etc/my.cnf有提醒的先移除然后再做下一步。3.2 关闭服务、备份数据目录、替换二进制干净关闭5.7是首要动作。不要用kill -9去杀mysqld进程那样可能造成redo日志未完全落盘升级时数据字典初始化阶段就容易出问题。正确方式是mysqladmin -uroot -p shutdown然后确认进程已经退出ps -ef | grep mysqld确认没有mysqld进程后对数据目录做一次整体副本。这里的整体副本指保留一份完整的、干净的5.7数据目录而不是只做逻辑备份。物理副本是升级失败后最可靠的回退依据。cp -a /var/lib/mysql /var/lib/mysql_57_backup接下来替换二进制。以8.0.36为例cd /usr/local tar -xzf mysql-8.0.36-linux-glibc2.17-x86_64.tar.xz ln -s /usr/local/mysql-8.0.36-linux-glibc2.17-x86_64 /usr/local/mysql如果原5.7的二进制软链指向的是/usr/local/mysql替换软链后就完成了二进制切换数据目录、配置文件保持原路径不变。做这一步之前务必确认目录权限chown -R mysql:mysql /usr/local/mysql3.3 启动8.0理解自动升级机制从8.0.16开始mysql_upgrade命令被废弃了数据字典和系统表的升级由mysqld在启动时自动完成。第一次启动8.0日志里会出现类似这样的信息[System] [MY-013178] [Server] Starting upgrade of user data... [System] [MY-013179] [Server] Upgrade of user data completed successfully.升级过程中会初始化新的数据字典表、更新系统表结构、转换旧时间格式等等。这个阶段千万不要用--skip-grant-tables启动也不要同时拉起多个mysqld实例指向同一个数据目录否则数据字典并发初始化会把系统表搞坏。正常启动命令不变systemctl start mysqld或者手动方式/usr/local/mysql/bin/mysqld_safe --usermysql 启动后第一时间看日志末尾有没有ERROR级别的记录然后确认版本/usr/local/mysql/bin/mysql -uroot -p -e SELECT VERSION();看到8.0.36输出后不要急着欢呼。完成自动升级只代表数据字典层面成功了业务层的验证才刚刚开始。3.4 升级后的首轮修正动作新实例起来后我会按固定顺序做一组操作检查系统表状态SHOW TABLES FROM mysql;是否完整确认所有业务库的表都还在对比升级前后information_schema.tables的总数检查root账户的认证插件按需改回兼容模式用SHOW VARIABLES LIKE sql_mode;确认当前模式和升级前记录做对比重新评估关键参数比如把innodb_buffer_pool_size调整到物理内存的60%~70%。其中第5点容易被忽略。很多5.7实例的innodb_buffer_pool_size设置得很保守到了8.0完全可以用更大内存换性能这是升级的隐藏福利不用白不用。4. 升级后最容易翻车的五个场景与完整排查链路这一节写的是我见过、也处理过的真实翻车现场。每个场景都按现象-排查-解决的链路来说希望你能在升级前就建立排查直觉而不是等问题出现后手忙脚乱。4.1 启动直接失败废弃配置项和未知变量现象替换二进制后执行启动MySQL进程起不来日志里报unknown variable query_cache_typeON排查链路先看error log里说的是哪个变量然后逐个核对my.cnf。最坑的是配置文件里同时有多个废弃参数日志可能只报第一个导致你改一个启动一次反复折腾。我的做法是先把配置里所有非标准参数全部注释掉启动成功后再逐步恢复真正需要的参数。这里说的非标准参数指的是不确定8.0还认不认的参数。恢复时用SHOW VARIABLES LIKE确认参数存在且值生效后再放行。经验补充如果使用的是Docker方式部署的MySQL注意容器镜像里的配置文件路径和格式有些镜像会把自定义配置放在/etc/mysql/conf.d或/etc/mysql/mysql.conf.d查配置的时候别只盯一个文件。4.2 客户端握手异常认证插件不兼容现象应用报错连不上库报错信息长这样java.sql.SQLException: Unable to load authentication plugin caching_sha2_password排查链路确认服务端是否正常mysql -uroot -p能本地登录说明实例本身没问题确认客户端连接对象是Java的Connector/J还是PHP的mysqlnd还是Navicat查驱动版本Connector/J 5.x基本无法支持caching_sha2_password必须升到8.xPHP 7.2以下的mysqlnd也不支持如果业务驱动无法快速升级临时方案是把对应账号的认证插件改回旧版ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;这里特别提醒一句不要把default_authentication_plugin直接改回mysql_native_password来图省事。这样做确实能让所有新用户都走旧插件但等于把8.0最重要的安全升级之一给废掉了。临时账号过渡可以理解全局降级一定要避免。4.3 SQL被拒ONLY_FULL_GROUP_BY的连锁反应现象升级完成后业务监控和后台开始大量报SQL错误Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column排查链路从慢日志或者业务日志里抓到报错SQL确认报错原因GROUP BY的SELECT列表里包含了非聚合列且没有写进GROUP BY查看当前sql_mode是否包含ONLY_FULL_GROUP_BYSHOW VARIABLES LIKE sql_mode;如果之前5.7为了兼容业务把这个模式移除了8.0继承了自定义配置那这个报错不一定出现但如果用的是8.0默认配置大概率会中招。解决的核心是改SQL不是改全局配置。比如前面那个例子如果业务确实需要返回b列的任意值用ANY_VALUE(b)SELECT a, ANY_VALUE(b), COUNT(*) FROM t GROUP BY a;如果是确实需要按a分组但想取每个分组内某列的最大/最小那就用子查询或者窗口函数。8.0已经支持窗口函数了只要一行ROW_NUMBER()就能解决很多老SQL的写法这也是升级带来的红利。4.4 时间字段数据错乱时区参数不一致现象升级后业务反馈新增数据的时间比实际时间早/晚8小时查询出来的历史时间也不对。排查链路检查服务端时区SELECT global.time_zone, session.time_zone;检查客户端连接参数Java的Connector/J有没有配serverTimezoneAsia/Shanghai对比升级前后的连接参数如果5.7时代应用配的是serverTimezoneGMT8而8.0默认time_zone是SYSTEM在某些云主机系统时区是UTC的情况下时间偏差就出现了。解决方案很直接统一时区。服务端设置成明确的时区而不是SYSTEMdefault-time-zone 08:00同时把应用层的连接串参数校准避免两层各动各的反而叠出更多偏移。这类问题升级前不容易暴露因为5.7的驱动和默认时区可能刚好和业务匹配升级后任何一个连接参数变了时间就跟着错了。4.5 大表升级耗时失控旧时间格式的隐性代价现象升级检查器报告里出现过WARNING: Schema contains tables with obsolete temporal columns当时没当回事。结果启动升级后某个上亿行的表停留在一个时间点很久整个升级过程远超预期。排查链路升级前用检查器扫描到的具体表名去该表确认列类型SHOW CREATE TABLE table_name;看是否存在timestamp或datetime列确认MySQL 8.0会自动把旧时间格式转换为新格式这个过程要重写表数据所以耗时取决于表大小和磁盘IO如果这张表实在太大升级窗口不够用就要回到第2节说的主从滚动路线在从库上慢慢升级而不是在业务高峰硬扛。这里我要强调一个实操细节旧时间格式转换是在启动阶段全自动执行的你在mysql客户端里看不到任何进度条容易产生是不是卡死了的错觉。判断是否还在升级中可以开另一个终端执行SHOW PROCESSLIST;或者看error log里面会持续输出升级进度相关的状态。不要一看到日志长时间没变化就手动杀进程先区分是正在底层处理大表还是真的hang住。5. 回退预案与升级后的持续观察很多团队做升级只想着怎么升上去很少提前想万一挂了怎么退回来。而恰恰是回退预案的完整度决定了你在升级过程中的心态和决策质量。5.1 降级为什么不是换回二进制这么简单MySQL官方明确不支持从8.0降级到5.7原因在于8.0启动后会修改数据字典数据字典版本无法自动回滚。一旦8.0实例对某个数据目录完成了初始化再拿5.7的二进制去启动这个目录基本是启动不了的。所以真正可靠的回退方式是依赖升级前保留的5.7数据目录副本和原版二进制。具体的回退操作流程停止8.0实例把被8.0启动过的数据目录整体移走不要删除留着后续分析把升级前备份的/var/lib/mysql_57_backup恢复为/var/lib/mysql恢复5.7的二进制软链启动5.7实例确认数据和日志状态。这里面有一个前提容易被忽略升级前备份数据目录时业务必须处于停写状态否则备份后的增量写入在升级过程中可能丢失。所以哪怕你心里已经打算用回退方案停机窗口内也一定要先把数据停干净、备份干净再动二进制。如果不想停机又想保留回退能力就回到主从滚动方案切换之后旧5.7主库不是立刻销毁而是保留一段时间甚至继续追8.0从库的日志。真出问题业务还能切回5.7主库只是要接受主库在升级期间积压的变更需要手动对账——这个复杂度远超原地升级但对关键业务来说是值得的。5.2 升级后一周的观察清单升级不是以SELECT VERSION() 显示8.0为结束标志。根据我的经验升级后至少一周内要持续盯几类指标慢查询数量对比8.0的优化器和5.7有明显差异有些以前走索引的查询在新的统计信息下可能走全表扫描慢查询日志是最直接的反馈连接数与连接失败率重点看认证插件兼容后的连接异常是否还在增长复制延迟如果还有老从库在复制8.0主库或者8.0从库复制别的主库延迟情况每天记录一次磁盘空间增长8.0的redo log和undo log管理机制发生变化空间使用曲线要盯住业务侧的错误日志让开发把升级后一周应用日志里的数据库异常关键词捞出来比如Unknown column、syntax error、lock wait timeout这些往往比数据库本身的监控更早暴露问题。观察期内尽量不要马上引入新特性。很多团队升级完8.0立刻想把窗口函数、CTE、不可见索引全部用上我建议先稳一周确认基础功能没问题再在非核心库上试点新特性。8.0升级本身已经改变了执行计划和查询路径再叠加新SQL写法出问题都没法定位。5.3 一个让升级更从容的小技巧升级前在5.7实例上把performance_schema和慢日志统计打开记录升级前一周的TOP慢SQL升级后拉同一批SQL做对比。这套数据比任何官方工具的检查结果都更贴近你的真实业务能帮你快速确认这次升级到底是优化了还是劣化了线上的查询表现。具体做法不复杂SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;然后把慢日志和sys.statement_analysis里的TOP查询存一份快照。升级后同一时间窗口内再看一遍差异一目了然。按我自己操作下来的感受MySQL 5.7到8.0的升级最难的不是安装配置教程里那些命令而是对存量业务影响的预判和兜底设计。把检查器跑清了、把路径选对了、把备份做全了剩下的就是耐心的验证。如果你正在规划升级希望这篇带有细节的实践记录能给你一些参考。