
MySQL 5.7 在 2023 年 10 月正式结束了官方支持这意味着安全补丁和 bug 修复都已经停更。我这边线上还有几十套 5.7 实例从年初开始陆续推进升级最近刚把最后一套核心业务库切到 8.0整个过程踩了不少坑也沉淀了一套可以复用的流程。这篇东西就是把这轮升级从评估、备份、原地升级到验证调优的完整过程梳理一遍给正准备做同样事的同学一个参考。先说结论MySQL 8.0 不是 5.7 的简单小版本迭代它改了数据字典、默认字符集、认证插件、SQL 行为升级不当轻则应用报错重则数据不一致。但只要按一套稳妥的流程走风险完全可控。我这篇主要讲原地升级in-place upgrade路径这也是单实例场景下最省事的方案。1. 升级前必须想清楚的三件事1.1 为什么要升级5.7 停服风险与 8.0 核心红利很多人对升级的态度是能用就不动但 5.7 停止维护带来的影响是实打实的。官方不再发布安全补丁一旦出现新的高危漏洞你的数据库就暴露在风险里。合规审计那边也迟早会问到这个问题与其被动应对不如主动规划。MySQL 8.0 带来的核心变化我挑几个对业务有实际感知的数据字典重构8.0 用 InnoDB 存储元数据取代了 5.7 的 .frm 文件和 MyISAM 系统表。字典操作变成事务性的DDL 崩溃恢复更可靠。默认字符集变为 utf8mb45.7 默认是 utf8mb3实际就是 utf8遇到 emoji、生僻字容易出问题。8.0 默认 utf8mb4配合新的排序规则 utf8mb4_0900_ai_ci数据存储和排序行为都有改善。窗口函数和 CTE这部分对开发同学是重大利好。以前写排名、累计值要绕道临时变量或子查询8.0 直接ROW_NUMBER()、LAG()就能搞定SQL 可读性和维护性上了一个台阶。认证插件改为 caching_sha2_password安全强度更高但也带来兼容问题——老版本的 JDBC 驱动、客户端工具连不上后面我会专门讲这个坑。原子 DDL像ALTER TABLE这类操作要么完整执行要么完全不执行不会出现 5.7 里 DDL 中途失败留下半成品状态的情况。还有不可见索引、降序索引、资源组、持久化系统变量等实用特性这些听起来新鲜但真正用起来后你会发现 8.0 值得升级。1.2 升级路径选型原地升级还是逻辑迁移升级到 8.0 有两条主流路线我分别说说适用场景。原地升级in-place停服备份用 8.0 二进制替换 5.7启动后执行升级检查。优点是快数据文件不用重新导入适合单机或主从环境、数据量大的场景。缺点是升级过程需要停机窗口且一旦执行了升级操作不能直接回滚到 5.7必须依赖升级前的全量备份恢复。逻辑迁移logical migration用mysqldump或 MySQL Shell 的导出导入工具把数据从 5.7 导出再导入 8.0。优点是可以在新实例上先验证风险更低缺点是数据量大的时候耗时很长几十 GB 可能要跑几个小时甚至一天而且导入时索引重建很吃资源。我的建议是能用原地升级就用原地升级。数据量在 100GB 以内且允许较长停机窗口的逻辑迁移也可以接受但如果你的库是几百 GB 甚至上 TB逻辑迁移的时间成本会让你崩溃。我这次线上最大的实例是 800GB原地升级加校验总共花了不到 40 分钟停机时间这个量级用逻辑迁移根本不敢想。注意无论走哪条路升级前必须做全量备份。原地升级后 5.7 的数据文件目录被 8.0 改了想回退只能靠备份恢复这没有例外。2. 升级前的全面体检2.1 兼容性检查清单升级前最忌讳直接拿生产库开干。我每次都会先做一轮全面检查把可能出问题的点提前列出来。检查项一版本与系统环境5.7 必须是较新的小版本官方要求至少 5.7.24 以上才能原地升级到 8.0。操作系统是 64 位 Linuxglibc 版本要满足 8.0 的要求。我用的 CentOS 7.9 没问题如果你还在用 CentOS 6 这类老系统先换系统再谈升级。磁盘空间要预留充足。8.0 升级过程中 InnoDB 数据字典重建会消耗额外空间我一般预留数据文件大小 1.5 倍以上的剩余空间。检查项二SQL 兼容性8.0 对 SQL 行为有一批收严的改动最典型的是sql_mode。5.7 默认没开ONLY_FULL_GROUP_BY很多老业务的 GROUP BY 写法是宽松模式下的——只 select 没在 GROUP BY 里的列这种 SQL 在 8.0 默认配置下直接报错。还有NO_AUTO_CREATE_USER在 8.0 里被移除了GRANT语句里顺手创建用户的写法会失效。我用官方提供的MySQL Shell 升级检查器Upgrade Checker Utility跑了一遍全库 SQL 模式检查它会扫描所有存储对象的定义把不兼容的地方列出来。命令很简单mysqlsh --uri rootlocalhost:3306 --js util.checkForServerUpgrade()这个检查器会输出三类结果错误必须修复、警告建议修复、注意提示信息。我当时检查出一批问题后面实战部分会细说。检查项三废弃功能和默认值变更8.0 移除了一些 5.7 里就已经标记废弃的功能比如mysql_native_password插件默认不再启用但为了兼容还可以手动开启。sys库里的部分存储过程和函数有调整。部分系统变量被移除或改名比如query_cache_size相关的参数在 8.0 里完全失效。如果你的 my.cnf 里配置了这些废弃参数启动 8.0 时进程可能直接起不来日志里会明确告诉你哪个变量未知。所以升级前要清理一遍配置文件把废弃参数摘掉。2.2 备份策略与回滚预案这一步是底线不能省。我习惯做三层防护全量物理备份用xtrabackup对 5.7 做一次完整的物理备份备份文件拷贝到独立存储。原地升级后如果发现问题用这份备份恢复到 5.7 环境。逻辑备份额外跑一份mysqldump包含所有库、表结构、数据、触发器、存储过程和事件。逻辑备份放在另一台机器上防止物理备份文件损坏时无路可退。binlog 归档确认 binlog 开启且连续记录下备份点对应的 binlog 文件名和位置。如果升级后需要追数据可以实现基于时间点的恢复。备份命令参考# 物理备份 xtrabackup --backup --target-dir/backup/mysql57_full \ --userbackup_user --password*** --hostlocalhost # 逻辑备份含存储过程、触发器、事件 mysqldump --single-transaction --routines --triggers --events \ --all-databases --set-gtid-purgedOFF /backup/mysql57_all.sql备份完成后我还要做一次恢复演练在测试机上把备份恢复出来确认数据可用、服务能正常启动。别等到出问题才第一次尝试恢复那样风险太大了。回滚预案也要提前写好。原地升级失败时的标准动作是停掉 8.0 进程清空数据目录用 xtrabackup 恢复 5.7 备份改回原配置文件启动服务验证业务。整个回滚流程我建议在测试环境先演练一遍真正出问题时照着操作手册执行就行不用临场想。3. 核心实操原地升级全流程3.1 环境准备与二进制替换这一节我按实际操作的顺序来写。假设你的环境是Linux MySQL 5.7.44数据目录在 /data/mysql端口 3306。第一步停业务、刷脏页、做最终备份选择业务低谷期比如凌晨。先把应用流量摘掉或者只保留只读然后执行SET GLOBAL innodb_buffer_pool_dump_now ON; SET GLOBAL innodb_buffer_pool_shutdown_now ON;这两条命令分别把 buffer pool 里的热数据页 dump 到磁盘以及在关闭时快速刷新脏页。目的是让停机过程尽量短。接着正常停库systemctl stop mysqld # 或者 mysqladmin -uroot -p shutdown停库后确认进程真的退出了再做一个快速物理备份确保有一个刚刚停服时的干净状态。第二步备份并替换二进制先记录当前版本和配置/usr/local/mysql/bin/mysql --version cat /etc/my.cnf然后把 5.7 的安装目录改名保留解压 8.0 的二进制包mv /usr/local/mysql /usr/local/mysql_57_bak tar -xzf mysql-8.0.36-linux-glibc2.12-x86_64.tar.xz -C /usr/local mv /usr/local/mysql-8.0.36-linux-glibc2.12-x86_64 /usr/local/mysql注意数据目录不要动/data/mysql 下的文件还是 5.7 的格式8.0 首次启动时会自动升级数据字典。第三步清理配置文件这一步特别容易踩坑。8.0 对不认识的配置项是直接拒绝启动的。我建议把 my.cnf 里这些参数全部删掉或注释query_cache_type、query_cache_size查询缓存已移除innodb_file_formatInnoDB 文件格式已统一key_buffer_size仅对 MyISAM 有效8.0 里系统表不再是 MyISAMlog_queries_not_using_indexes已被其他参数替代同时把认证相关配置加上这里有一个兼容性选择。如果业务端的 JDBC 驱动、PHP 扩展、Python 客户端都已经是新版本支持 caching_sha2_password那什么都不用做如果还有些老客户端一时换不掉我建议在 my.cnf 里临时加一行default-authentication-pluginmysql_native_password不过要注意这是临时方案。等所有客户端升级后还是应该切回默认的 caching_sha2_password。数据库实例如果有运行专用账号8.0 有个变化需要注意mysql系统库的表结构变了5.7 里手动建的用户账号、授权信息在升级时会自动迁移但权限粒度更细了。比如SUPER权限在 8.0 里拆成了SYSTEM_USER、SYSTEM_VARIABLES_ADMIN等多个细权限如果你的监控账号依赖 SUPER 权限做某些操作比如设置全局变量升级后可能需要重新授权。这部分我建议在测试环境先验证一遍监控系统的权限是否够用。3.2 启动升级与系统表处理第四步首次启动 8.0改好配置后用 8.0 的二进制启动服务systemctl start mysqld或者直接前台启动看日志/usr/local/mysql/bin/mysqld --usermysql --datadir/data/mysql --console首次启动时 8.0 会自动检测到旧的 5.7 数据字典执行升级动作。日志里能看到类似这样的信息[Note] [MY-010768] [Server] Upgrading from MySQL 5.7 to MySQL 8.0 [Note] [MY-011066] [Server] Checking for invalid tables ... [Note] [MY-010951] [Server] The upgrade is done successfully升级过程会重建数据字典、转换系统表、校验所有表的兼容性。数据量大的实例这里会比较慢我那套 800GB 的库花了大概 15 分钟。这期间日志会频繁输出千万别中途 kill 进程宁可等。第五步MySQL 8.0 中手动跑升级校验老方法5.7 时代讲升级都要手动执行mysql_upgrade8.0 之后不需要了——从 8.0.16 开始升级动作在服务启动时自动完成。不过还是可以手动跑一下确认状态/usr/local/mysql/bin/mysql_upgrade --force这个命令会再次检查所有数据库对象并更新统计信息跑完会告诉你有没有错误。其实上面日志显示 upgrade is done successfully 就说明系统表层面没问题了不过我还是习惯手动跑一遍安心。第六步重启并确认版本升级完成后再正常重启一次确保下次启动也是干净的systemctl restart mysqld /usr/local/mysql/bin/mysql -uroot -p -e SELECT VERSION();输出应该是8.0.x同时检查关键参数SHOW VARIABLES LIKE default_authentication_plugin; SHOW VARIABLES LIKE character_set_server; SHOW VARIABLES LIKE sql_mode;这里要核对三件事认证插件是否符合你预期的兼容方案、字符集是否是 utf8mb4、sql_mode 是否包含 ONLY_FULL_GROUP_BY。4. 升级后的验证与调优4.1 数据一致性验证升级完成不代表结束数据验证这步我做得非常细。表结构与对象检查先确认所有库表都在、行数和升级前一致。我写了个简单的统计脚本遍历所有库比对SELECT table_schema, COUNT(*) FROM information_schema.tables WHERE table_schema NOT IN (mysql,sys,performance_schema) GROUP BY table_schema;重点要检查的对象包括存储过程、触发器、视图、事件、外键约束。这些在 5.7 升级到 8.0 时可能会因为 SQL_MODE 收紧而创建失败需要用下面的方式逐一确认SELECT ROUTINE_NAME, ROUTINE_DEFINITION FROM information_schema.routines WHERE routine_schema your_db;数据抽样比对我习惯对几张核心大表做抽样校验比如取主键范围内的若干条记录比较升级前后的字段值。如果有测试环境可以在升级前跑一套完整的集成测试用例升级后回归一遍比人工检查高效得多。字符集问题排查5.7 的老库常有字符集不统一的问题——库是 utf8mb4表却是 latin1。8.0 默认字符集是 utf8mb4但已存在的表不会自动转换。我升级后遇到过一个情况某张历史表的字段是 latin1应用端写入中文没事但读取后转成 UTF-8 出现乱码。排查方式SHOW TABLE STATUS FROM your_db LIKE your_table;发现Collation列是latin1_swedish_ci确认就是它的问题。用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4处理后恢复正常。这个动作建议在业务低峰期做因为会重写整张表。4.2 性能回归测试与参数调整数据验证通过后就要考虑性能问题了。8.0 的默认参数和 5.7 差异不小直接沿用老配置的话性能可能不升反降。先说一个最容易踩的坑buffer pool 预热由于升级后服务重启过buffer pool 是冷的。第一次业务请求会明显感觉变慢这个不算 bug是缓存冷启动的正常现象。我升级前特意加了innodb_buffer_pool_dump_at_shutdownON和innodb_buffer_pool_load_at_startupON重启后 InnoDB 会自动把之前的 buffer pool 内容加载回来能省掉相当长的预热时间。8.0 推荐关注的参数我对照 5.7 到 8.0 的参数变化整理了一份常用参数对照表参数5.7 典型值8.0 建议说明innodb_buffer_pool_size物理内存 50%-70%同左可适当调大8.0 的 buffer pool 管理更高效innodb_buffer_pool_instances按池大小默认即可8.0 自动调整实例数innodb_flush_log_at_trx_commit11保持默认数据安全优先innodb_log_file_size256M-1G8.0 用 innodb_redo_log_capacity8.0 改用 redo log 容量参数max_connections按业务同左注意认证开销caching_sha2_password 握手开销略大innodb_flush_methodO_DIRECTO_DIRECT保持别乱改特别要提innodb_log_file_size这个参数它在 8.0.30 之后被innodb_redo_log_capacity取代旧参数虽然还能识别但会告警。我在升级后就把 redo log 容量改成了innodb_redo_log_capacity 8G从实际效果看大量写入场景下的 redo log 切换频率明显降低。跑一遍压测有条件的话用sysbench或mysqlslap做一轮基础压测对比 5.7 和 8.0 的吞吐。我实际测下来的结果纯读场景 8.0 有 10%-20% 的提升写场景差异不大复杂 SQL多表 JOIN、子查询因为优化器改进有明显提升。但这只是参考不同业务负载差异很大别拿我的数字当你的预期。慢查询日志排查升级后一定要开启慢查询日志跑几天看看有没有新出现的慢 SQL。我这次升级后发现一条以前 20ms 的 SQL 变成 800ms排查下来是 8.0 优化器对某个 JOIN 选择了不同的执行计划加了正确的索引后恢复。这类问题在升级后出现的概率不低提前开启慢查询日志就是给排查留证据。5. 高频故障与排查实录5.1 连接报错认证插件不兼容这是升级后最常见的坑没有之一。现象是应用端报Authentication plugin caching_sha2_password cannot be loaded或者 JDBC 报Public Key Retrieval is not allowed原因上面提过8.0 默认认证插件从mysql_native_password换成了caching_sha2_password老客户端驱动不认识新插件。解决方案分两层第一层升级客户端驱动。Java 用 Connector/J 8.0.xPHP 用 mysqlnd 7.4Python 用 mysql-connector-python 8.0.x。驱动升级不需要改代码只换 jar 或扩展包。第二层实在换不了驱动的把账号改回旧认证方式ALTER USER app_user% IDENTIFIED WITH mysql_native_password BY password;或者全局默认改成旧插件前面 my.cnf 里加过了。我实际遇到的情况是某套老系统用的是 2016 年的 PHP 驱动短期没法升级只能在账号层面暂时用mysql_native_password顶着。等系统改造完再转回来。注意caching_sha2_password首次连接时如果是 SSL 加密通道没问题如果不是 SSL客户端必须先做一次 RSA 公钥交换才能完成认证。JDBC 驱动可能需要显式配置allowPublicKeyRetrievaltrue这个配置项在网络不可信环境要慎重建议配合 SSL 使用。5.2 SQL 行为变化GROUP BY 和保留字GROUP BY 报错升级后最常见的 SQL 报错是ERROR 1055: Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column ...这就是ONLY_FULL_GROUP_BY模式收紧导致的。5.7 里这个模式默认其实是开启的但很多老实例从 5.6 升上来时保留了宽松配置业务代码里就累积了一堆不合规的 GROUP BY。我处理这类问题的原则是改 SQL不改全局配置。逐条把 select 的列要么放进 GROUP BY要么用聚合函数包起来。虽然可以把sql_mode里的ONLY_FULL_GROUP_BY去掉图省事但那样会让错误的 SQL 一直潜伏以后换到其他数据库比如 PostgreSQL更痛苦。保留字冲突8.0 新增了一些保留字之前字段名不冲突的在升级后反而报错。典型的是groups、rank、window、cume_dist这些窗口函数相关词。解决方案是 SQL 里用反引号包裹或者干脆改字段名。我这次就中招了一个rank字段改了应用 SQL 才恢复。5.3 启动失败与性能回退启动失败配置项不认识现象是systemctl start mysqld后马上失败看错误日志[ERROR] [MY-000077] [Server] Unknown system variable query_cache_type这个就是配置里还有废弃参数没清干净。我列过一个废弃参数清单最常踩的是 query_cache 全家桶、老版本的 innodb_file_format。处理办法就是注释掉后重新启动。性能回退执行计划变化升级后慢 SQL 变多了不一定是参数问题很可能是优化器的选择变了。8.0 的优化器对 JOIN 顺序、子查询处理、索引选择都有调整。排查步骤用EXPLAIN ANALYZE看新的执行计划。对比 5.7 时期的执行计划所以我说升级前要保留一份EXPLAIN快照。如果是索引选择问题先优化索引不要急着加 hint。实在不行才用 hint 固定执行计划但要做好注释说明避免未来换库时踩坑。5.4 常见问题速查表我把升级过程中遇到的问题整理成一张速查表方便大家直接对照现象原因快速处理客户端连不上报 caching_sha2_password 无法加载客户端驱动过老升级驱动或临时改账号认证插件JDBC 报 Public Key Retrieval is not allowed未开启 RSA 公钥获取JDBC URL 加 allowPublicKeyRetrievaltrue配合 SSLSELECT 报错 1055ONLY_FULL_GROUP_BY 生效改写 SQL不用全局配置规避字段名报语法错误字段名是 8.0 新增保留字反引号包裹或改名服务启动失败Unknown system variable配置残留废弃参数删除/注释废弃参数升级后首次访问慢buffer pool 冷启动开启 dump/load buffer pool 参数部分中文显示乱码老表还是 latin1 字符集ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4存储过程或触发器缺失sql_mode 变更导致创建失败检查 information_schema重新创建并修正定义这张表建议打印出来贴在工位上升级期间遇到问题直接查。6. 几点实战心得6.1 升级窗口一定要留足余量我最初给核心库排的停机窗口是 30 分钟实际用了 40 分钟——多出来的时间是首次启动时数据字典转换比预期慢了些加上安全起见我多跑了一遍校验。真实生产环境里磁盘 IO 负载、系统整体状态都会影响升级耗时窗口至少按预计的 1.5 倍留。6.2 先升级从库再切换再升级主库如果是主从架构我强烈建议用这套顺序先给从库做原地升级观察几天确认没有报错和数据差异然后把主从切换让升级后的 8.0 从库顶上来接写流量最后再把老主库升级成新的从库。这样全程业务无感而且每步都可回退。我这次就是按这个思路做的心理压力小很多。6.3 不要忽略监控和备份工具链升级后我发现一个问题监控用的 Prometheus mysqld_exporter 版本太老采集 8.0 指标时部分数据为空备份脚本里用的 xtrabackup 版本也不兼容 8.0需要升级到 8.0 对应版本。这类工具链问题不影响核心业务但会让运维陷入数据裸奔状态。所以我建议把监控、备份、同步相关的所有周边工具在升级前就纳入检查清单。6.4 最后的建议如果你现在还在 5.7 上别拖。越往后拖业务累积的不兼容 SQL 会越多工具链的兼容成本也会越来越高。升级本身没有想象中那么可怕但准备工作决定成败。先把测试环境完整走一遍流程记录每一步的耗时和日志然后再上生产你会发现整个过程可控、可预期。最后分享一个小技巧我在测试环境升级时把升级前后的information_schema全量导出一份做了 diff哪个表少了、哪个字段变了一目了然。这个笨办法帮我发现了两次潜在问题比任何自动化工具都管用。你要是也准备升级这个技巧可以直接抄过去用。