1. 前言
2015 年 10 月,Oracle 正式推出 MySQL 5.7,相比 MySQL 5.6,其具备更好的性能、更多的功能、更高的安全性,逐步成为了 MySQL 的首选版本。2018 年, MySQL 8.0 发布,相比 MySQL 5.7 有了进一步的提升 。2023 年 10 月 25 日,社区推出 MySQL 5.7.44,该版本为最后一个 5.7 版本,意味着社区将停止对 MySQL 5.7 的维护,不再进行缺陷的修复和功能的更新,同时社区也鼓励用户将 MySQL 5.7 升级到 MySQL 8.0[1-2]。
图 1 MySQL 生命周期
事实上 MySQL 5.7 作为备受信赖的服务版本,现今依然拥有广大的用户群体。据 Shadowserver 数据统计[3],截止至 2022 年 5 月,在 MySQL 的使用者中,约有 47% 的人仍在坚持使用 5.7 作为主要服务版本。在阿里云 RDS MySQL 实例列表中,MySQL 5.7 的使用比例也达到了相近的数量。然而,随着生命周期结束,由于缺乏后续的更新和支持,不再维护的 MySQL 5.7 面临着种种问题,难以满足不断变化的需求。
图 2 社区MySQL主要服务版本
相对于 MySQL 5.7,MySQL 8.0 中带来了众多强大、实用的特性,如:
表 1 MySQL 8.0 部分新特性
功能 |
收益 |
DDL log |
解决了 MySQL 5.7 中 DDL 中断的文件残留问题,确保了文件操作的原子性,同时解决了 Server 层和引擎层数据不一致的问题。 |
Instant DDL |
大大缩短 DDL 执行的时长,快速完成表结构的变更,同时消除了 Binlog 复制延迟。 |
增强 JSON 支持 |
新增了多个 JSON 相关的函数,同时对 JSON 的内存使用做了优化。 |
隐藏索引 |
可设置索引对优化器不可见,避免了潜在的重复删除、重建索引的操作。 |
克隆插件 |
免去传统备份恢复 + 主从复制的方式,直接在源实例上授权、克隆,大大提高了可拓展性。 |
与此同时,阿里云 RDS MySQL 8.0 在社区 MySQL 8.0 的基础上做了深度优化,开发了许多高级特性[4],如 Binlog in Redo 功能通过在事务提交时将 Binlog 内容同步写入到Redo Log中以减少磁盘操作,提高了数据库性能;通用云盘 IO 加速功能通过扩展InnoDB Buffer Pool将一部分数据页和温数据缓存,在无任何成本变化和业务改动的情况下,可以获得实例 IO 性能的大幅度提升;冷热数据分离功能通过将云盘归档数据转存至 OSS 端,大幅降低用户存储成本;RTO 相关优化通过优化 REDO 应用过程和启动路径等,大幅缩短实例启动时间,提升实例的可用性。通过架构的持续演进和内核的不断优化,阿里云 RDS MySQL 相较社区版本拥有更高的稳定性和安全性、更强的性能和更多的功能,为用户提供稳定可靠、简单易用的数据库服务。
更多 RDS MySQL 自研的高级特性可参考阿里云 RDS 官方文档。
作为更强大的分支,MySQL 8.0 的维护周期将会更长久,也会逐步解决现有问题。长久来看,无论是个人用户还是企业用户,都应该推动 MySQL 5.7 到 MySQL 8.0 的升级。
2. 升级前的检查
图 3 MySQL 8.0 与 MySQL 5.7 的主要差异
尽管有许多不得不升级的理由,但实际上从 MySQL 5.7 升级到 8.0 并不是一件轻而易举的事情。MySQL 8.0 与 MySQL 5.7 相比有许多跨度很大的变化,包括了数据字典、存储引擎和SQL语法等方面的改动。由于版本间的差异,在升级之前,用户需要充分评估并做好充足的准备工作。
和 MySQL 5.6 升级到 MySQL 5.7 不同,MySQL 5.7 升级到 MySQL 8.0 是通过 inplace upgrade 的方式进行的,直接使用 MySQL 8.0 的二进制文件拉起 5.7 旧实例,升级程序就会自动进行相关的检查和升级。前文所述的升级前的准备工作实际上就是去消除升级程序中无法自动处理的的“不兼容的”变化,主要工作包括[5-9]:
- MySQL 8.0 中引入了全新的数据字典用于保存数据库中的元信息,Server 层和 InnoDB 层共享一份元数据。8.0 中的 information_schema 中的视图全部源自于数据字典表,和 InnoDB 相关的视图被重命名(INNODB_SYS_XXX 重命名为 INNODB_XXX),若用户的应用依赖于这些视图,需要确保应用上做出对应的修改;在升级前需要确认业务是否依赖于 8.0 中被删除的系统表(如 mysql.proc、mysql.event 等),若存在则需使用新的访问方式去获取所需的数据;此外,对于和 8.0 系统表同名的用户表(如 catalogs、routines 等),需要手动执行 RENAME/DROP TABLE 操作。
- 8.0 不再支持的旧的数据类型(如 old style decimals, old style varchar, old style TIME、DATETIME 和 TIMESTAMP 等),问题表需要在升级前通过 REPAIR TABLE 或逻辑导出 + 导入的方式做修复。
- 分区表的处理在 8.0 中由 Server 层下沉至引擎层,MySQL Server 不再支持通用分区。 non-native 的分区表在 8.0 中被废弃,因此需要将此类的分区表修改为 native 类型的分区表(如 InnoDB 引擎)或是删除该分区表。
- 较老版本的 5.7(version < 5.0.17)的 trigger 不支持 definer 属性,因此在 5.7 实例中可能会存在缺失 definer 的 trigger,这将会导致升级到 8.0 的过程失败,因此需要在升级前重新创建此类 trigger 。
- 8.0 中对于外键约束的长度有限制,超长的外键约束会在升级的过程中报错导致失败,因此需要在升级前将问题外键修改为小于 64 字符长度的约束;8.0 之前视图的列名长度上限是 255 字符,而 8.0 对于视图的列名长度的上限是 64 字符,因此需要在升级前将视图的超长列名修改为小于 64 字符长度的列名;8.0 中单个 ENUM 和 SET 列元素的长度不得超过 255 个字符或 1020 个字节,因此需要在升级前超出限制的 ENUM 和 SET 进行修改。
- 若 frm 文件和 InnoDB 数据字典表元数据信息不匹配会导致错误,这种情况需要在升级前对数据做逻辑导出和导入;若存在游离的.frm 文件(即不含.ibd 文件仅有.frm 文件),则需要做相应的清理。
- 8.0 废弃了部分空间函数,如果生成列中包含了此类被删除的函数(新引入的空间函数以"ST"和"MBR"开头),则需要在升级前对对应的列做修改。
- 升级前必须保证 5.7 实例 clean shutdown ,即需要保证升级前没有待应用的 REDO log 和等待回滚的事务。
- 8.0 中引入了新的保留字,大多数的保留字禁止作为表名、列名等,因此需要对 5.7 包含 8.0 引入的新保留字的部分做处理。
- 使用废弃的 sql_mode 变量内容(如 NO_AUTO_CREATE_USER 等)会导致 8.0 实例无法启动,因此需要在升级前修改 sql_mode 至 8.0 支持的模式。
- 较新版本的 8.0 实例(version >= 8.0.13),共享表空间(系统表空间和通用表空间)中不能存在 InnoDB 类型的分区表,因此在升级前需要把共享表空间移动至独立表空间。
- 新版本的 8.0 实例不支持GROUP BY...[ASC | DESC]的语法,因此需要在升级前修改/删除包含相应句法的存储过程。
- 5.7 中 lower_case_table_names 是一个可修改的值,但在 8.0 中 lower_case_table_names 是一个初始化参数,实例一旦初始化就无法在后续做修改。若在升级时修改修改该参数为 1,需要确保升级前库表名称为小写,避免出现升级错误。
除了上述的准备工作外,版本间存在的驱动、错误码、参数、优化器差异等也需要业务上做适配。对于自建用户,5.7 到 8.0 的大版本升级过程前的准备可以利用 MySQL shell、mysqlcheck 工具和社区的升级检查文档进行检查。关于 5.7 到 8.0 的变化和检查细节可以参考文末的社区文档链接。
3. 为什么升级还是失败了?
尽管社区提供了相应的检查工具和帮助文档去帮助用户做升级前的检查和问题的解决,然而常常会发现升级过程还是失败了,抛出了各式各样的错误,难以从日志信息中去逐一分析升级失败的原因。分析困难的原因包括但不局限于以下几点:
- 报错不明确,不同原因导致的错误可能会在日志中记录相同/相似的错误。用户需要花费大量的时间和精力来排查错误的根本原因,增加了升级的困难度。
- 报错不完整,当问题出现时,针对性解决后发现重复的问题再次导致失败,原因是升级程序在首次检测到某一类错误时就提前退出了,相同问题无法被一次性全部检测。用户需要进行多次尝试和排查,增加了升级的复杂性和时间成本。
- 部分错误只给出错误提示,却没有对应的解法。用户需要自行查找解决方案或向社区寻求帮助,增加了升级过程中的困惑和不确定性。
- 部分 5.7 或更老版本存在的问题被修复,但依旧缺少对存量实例的处理。
笔者在解决大版本升级问题中发现,由于版本差异,加上存在更老的 5.x 版本信息残留,大版本升级涉及的问题琐碎繁多,升级失败的根源大多集中在数据字典上。
4. 轻松升级到阿里云 RDS MySQL 8.0
阿里云 RDS MySQL 拥有国内乃至亚洲最大规模的 MySQL 实例,每月有数十个的实例进行 5.7 到 8.0 的大版本升级。在此过程中,我们积累了大量的经验,搭建了完备的预检查逻辑并对多个大版本升级相关 Bug 进行了分析和修复。
笔者所在的阿里云 RDS MySQL 团队所做的 5.7 升级 8.0 的专项治理,旨在提前发现问题、平滑升级过程,提升大版本升级的成功率,让用户拥有更舒适的大版本升级体验,享受到阿里云 RDS MySQL 8.0 提供的高性能和可靠性。此外,RDS 将对 MySQL 各个版本提供更长期的支持[10],给用户留下更长的缓冲期。
表 2 社区 MySQL 和 阿里云 RDS MySQL EOF
数据库大版本 |
社区发布时间 |
RDS发布时间 |
社区生命周期结束时间 |
RDS停止支持时间 |
MySQL 8.0 |
2018年04月19日 |
2019年05月 |
预计2026年04月 |
预计2027年04月19日 |
MySQL 5.7 |
2015年10月21日 |
2016年11月 |
预计2023年10月25 日 |
预计2024年10月21日 |
在自建 MySQL 5.7 升级 8.0 的过程中,除去前期繁复的检查工作外,根据报错信息逐步进行修复的过程可能会持续多次,并且还存在问题难以修复导致无法升级的情况。整个升级过程停机时间长、返工次数多、耗费时间精力。
图 4 自建 MySQL 5.7 和阿里云 RDS MySQL 5.7 升级到 8.0 流程对比
针对线上常见大版本升级失败问题,阿里云 RDS MySQL 内核侧做了修复工作,当用户的实例存在修复覆盖下的问题时,升级过程将不会失败,可实现无感升级;对于内核侧无法直接修复的问题,阿里云 RDS MySQL 管控侧主动进行多项兼容性检测,在实例触发大版本升级动作前提前发现问题并做出提示,用户在无需重复启停的情况下就能够解决大部分问题,同时对于升级失败的节点会自动回滚到升级前的状态,不影响业务。相比自建用户的升级,用户在进行阿里云 RDS MySQL 的大版本升级时,无需复杂的检查、无需反复核查错误日志并逐一解决问题,仅仅需要点击控制台的升级按钮,一切的升级动作都将在后台完成。经过系列的治理,2023 ~ 2024 年阿里云 RDS MySQL 5.7 升级 8.0 的成功率达到了 97.3% 以上。
表 3 阿里云 RDS MySQL 5.7 升级 8.0 的部分治理工作
导致升级失败的问题 |
治理 |
是否需要用户进一步干预 |
Server与InnoDB字段、索引大小写不一致[11] |
在升级过程主动检测并修复 Server 层和 InnoDB 的不一致问题,用户无需手动干预即可直接实现升级。 |
无需用户操作 |
表、字段、索引和存储过程包含乱码[12] |
在升级过程中主动检测并清除无效的乱码信息,用户无需手动检查和处理;在预检查逻辑中定位涉及到数据的乱码信息,并向用户告知错误信息。 |
需要用户处理有效数据的乱码信息 |
5.x 老版本临时表残留[13] |
在升级过程中主动清除系统表中临时表残留的元信息,解决用户无法升级的问题。 |
无需用户操作 |
外键分区表不兼容[14] |
在预检查逻辑中定位包含外键和分区表冲突的表信息,方便用户提前处理。 |
需要用户解决表中外键和分区表的冲突 |
空间列和 B-tree 索引不兼容[15] |
在预检查逻辑中定位包含空间列和 B-tree 索引冲突的表信息,方便用户提前处理。 |
需要用户解决表中空间列和 B-tree 索引的冲突 |
外键约束、视图列名、enum/set 长度超限制 |
在预检查逻辑定位不满足长度要求的外键约束、视图列名和 enum/set 字段,方便用户提前处理 |
需要用户修改超出限制的长度 |
包含 discard 表空间 |
在预检查逻辑定位 discard 的表空间,方便用户提前处理 |
需要用户导入表空间或删除表信息。 |
包含待 repair 的表 |
在预检查逻辑定位 待 repair 的表,方便用户提前处理 |
需要用户执行 repair 的操作。 |
案例 1:Server与InnoDB字段、索引大小写不一致导致升级失败
自建 MySQL 升级失败:
[ERROR] [MY-013140] [Server] Comment for table 'test.t4' contains an invalid utf8mb3 character string: '\xF0\x9F\x90'. [ERROR] [MY-010022] [Server] Failed to Populate DD tables. [ERROR] [MY-010119] [Server] Aborting
RDS MySQL 自动修正引擎元数据,升级成功:
[Warning] [MY-012071] [InnoDB] Column name case mismatch: From InnoDB: id_c From Server: Id_c, DD will be consistent with Server one. [Note] [MY-011088] [Server] Data dictionary initializing version '80023'.
案例 2:表、字段、索引和存储过程包含乱码导致升级失败
自建 MySQL 升级失败:
[ERROR] [MY-013140] [Server] Comment for table 'test.t4' contains an invalid utf8mb3 character string: '\xF0\x9F\x90'. [ERROR] [MY-010022] [Server] Failed to Populate DD tables. [ERROR] [MY-010119] [Server] Aborting
RDS MySQL 自动清除表、字段和索引注释的乱码,升级成功:
[Warning] [MY-025076] [Server] Clear utf8mb3 character strings: '\xF0\x9F\x90' in comment of table 'test.t4'. [Note] [MY-011088] [Server] Data dictionary initializing version '80023'.
案例 3:5.x 老版本临时表残留导致升级失败
自建 MySQL 升级失败:
[ERROR] [MY-012216] [InnoDB] Cannot open datafile for read-only: '/tmp/#sql1dc6f_2_0.ibd' OS error: 71 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed. [ERROR] [MY-010119] [Server] Aborting
RDS MySQL 自动清理残留的元信息,升级成功:
[ERROR] [MY-012216] [InnoDB] Cannot open datafile for read-only: '/tmp/#sql8947_1_0.ibd' OS error: 71 [Warning] [MY-025078] [InnoDB] Skip orphaned tablespace during upgrade, space_id: 7, filepath: /tmp/#sql8947_1_0.ibd. [Note] [MY-011088] [Server] Data dictionary initializing version '80023'.
当然,阿里云 RDS MySQL 团队并不是完全根解了 MySQL 5.7 升级到 8.0 过程的所有问题,在场景上、方案上都有需要完善之处。当前大版本升级过程为了保证用户实例不受影响,升级过程首先会在源实例的克隆实例上进行,当克隆实例完成升级后再将用户实例做切换,并释放旧版本实例,这个过程难免会有更多的时间和资源的消耗,更好、更快的方案值得探索;当前的治理手段无法覆盖到所有的升级失败场景,无法保证 100%的升级成功率,同时用户行为和升级冲突的情形往往也需要用户介入去做进一步的处理,无法做到完全的全自动升级,进一步降低用户干预占比是需要持续努力的方向。
关于 RDS MySQL 的升级,更多的细节可以参考官方的升级帮助文档[16]。
5. 总结
升级前详细的检查、升级中逐步解决引发失败的问题、升级后做好验证和测试工作,这些都是大版本升级必须要关注的问题。大版本升级失败问题的解决不是一蹴而就的,阿里云 RDS MySQL 团队持续在进行相关问题的修复和建设,同时随着 8.0 版本走向稳定,相信大版本升级的问题会逐步收敛,直至完全解决。
6. 参考文档
[1] https://www.mysql.com/support/
[2] https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-44.html
[3] https://www.shadowserver.org/news/over-3-6m-exposed-mysql-servers-on-ipv4-and-ipv6/
[4] https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/overview-of-alisql-features
[5] https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html
[6] https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html
[7] https://dev.mysql.com/doc/refman/8.0/en/added-deprecated-removed.html
[8] https://dev.mysql.com/blog-archive/upgrading-to-mysql-8-0-here-is-what-you-need-to-know/
[9] https://dev.mysql.com/doc/refman/8.0/en/upgrade-prerequisites.html
[10]https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/major-version-lifecycle-description
[11] https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html
[12] https://bugs.mysql.com/bug.php?id=110177
[13] https://bugs.mysql.com/bug.php?id=110722
[14] https://bugs.mysql.com/bug.php?id=111134