Featured image of post 如何把mysql从5.7升级到8.0

如何把mysql从5.7升级到8.0

如何在生产环境中中升级MySql

当MySQL出现漏洞时,我们需要升级一下mysql,一般来说某些漏洞都会覆盖一些版本的,我我们需要选择没有漏洞的最小版本即可,比如说MySQL Server 存在输入验证错误漏洞,该漏洞源于MySQL服务器的Server: Parser组件内部输入验证不正确。以下产品及版本受到影响:MySQL Server: 5.7.0, 5.7.1, 5.7.2, 5.7.3, 5.7.4, 5.7.5, 5.7.6, 5.7.7, 5.7.8, 5.7.9, 5.7.10, 5.7.11, 5.7.12, 5.7.13, 5.7.14, 5.7.15, 5.7.16, 5.7.17, 5.7.18, 5.7.19, 5.7.20, 5.7.21, 5.7.22, 5.7.23, 5.7.24, 5.7.25, 5.7.26, 5.7.27, 5.7.28, 5.7.29, 8.0.0, 8.0.1, 8.0.2, 8.0.3, 8.0.4, 8.0.11, 8.0.12, 8.0.13, 8.0.14, 8.0.15, 8.0.16, 8.0.17, 8.0.18, 8.0.19,当我们修复该漏洞一般有两种选择,一是把官方发布的布丁打上,二是重新安装一个没有漏洞的版本。

在docker环境下,一般为了方便我们会选择第二种方式,只需要docker pull 然后配置一下替换掉原来的镜像即可。在选择升级版本的过程中,我们只需要选择没有当前漏洞且比当前版本大的那个最小的稳定版本就可以,比如上面的漏洞没有覆盖的5.7的最小的版本是5.7.30(注意:5.7版本没有LTS版本),所以理论上来说我们选择大于5.7.29且小于8.0.0的版本就可以。

不幸的是,我的cpu是arm架构的,而我没有找到介于上面版本区间的arm镜像,在这种情况下,我们就有了两种不同的抉择,一是自己构建并编译一个介于上面版本区间的arm版本的镜像,二是我们直接将mysql升级到8的LTS版本。对于第一种方式,我没有相关的编译经验,也没有相关的测试环境,于是我们选择了第二种方式。

按照我们选择版本的原则,理论上我们只需要选择版本为8.0.19之上的最小的稳定版本即可。但是在一开始升级我就没有遵循这条原则,我选择了8.4.6的版本。在换了该版本后会遇到各种问题没办法启动起来,最后翻mysql的官方文档看到了下面的内容:

mysql-update

大致的意思是说,如果从5.7升级到8.4的话,需要先升级到8.0,然后再从8.0升级到8.4。至此,踩完了升级过程中最大的坑。最后我选择了8.0.41这个LTS版本。下面是升级过程:

在5.7上执行SET GLOBAL innodb_fast_shutdown=0并干净关机

  1. 进入5.7容器(这里假设容器的名字是mysql57)
docker exec -it mysql57 bash
  1. 登录mysql:
mysql -uroot -p

回车后输入密码进入mysql。

  1. 设置慢关机参数(关键):
SET GLOBAL innodb_fast_shutdown=0;

然后查看是否设置正确:

SHOW VARIABLES LIKE 'innodb_fast_shutdown';

这里看到的值应该是0。

  1. 退出mysql:
exit;
  1. 在容器中用mysqladmin关掉mysqld:
mysqladmin -uroot -p shutdown

这一步会让mysqld正常退出,并写完redo日志。 如果不想在容器中操作,也可以在宿主机中执行:

docker exec -it mysql57 mysqladmin -uroot -p shutdown
  1. 确认容器已经退出:
docker ps -a | grep mysql57

现在它应该是Exit的状态。

经过上面的操作步骤我们干净的数据目录了。

⚠️ 注意: 不能直接 docker stop mysql57 就算关机,因为默认 innodb_fast_shutdown=1,它不会完全清 redo log,MySQL 8.0 就会拒绝升级。

做“目录级”拷贝(用于 8.0 升级)

假设我们5.7的容器名是mysql57,三个目录在宿主机的路径是:

  • 数据:/opt/mysql57/data
  • 配置:/opt/mysql57/conf
  • 日志:/opt/mysql57/logs

docker inspect mysql57 这个命令可以看到这三个挂载到容器内的位置,一般数据会挂载到/var/lib/mysql,配置会挂载到/etc/mysql/conf.d/etc/mysql/my.cnf,日志则取决于我们自己的my.cnf配置文件中的设置。

由于上一步我们已经把服务停掉了,这里直接使用rsync命令复制mysql57的文件即可(保留权限和软硬链接,保证mysql8可以读取到这些信息:

# 复制一套全新的目录,名字显示的标注给mysql8.0使用
mkdir -p /opt/mysql8/{conf, data, logs}

# 数据目录拷贝(最关键)
rsync -aHAX --info=progress2 /opt/mysql57/data/ /opt/mysql8/data/

# 配置目录拷贝
rsync -aHAX /opt/mysql57/conf /opt/mysql8/conf

# 日志目录拷贝(可选,或给8.0建空目录)
rsync -aHAX /opt/mysql57/logs/ /opt/mysql8/logs

# 确保属主属组为mysql,(官方镜像通常是mysql:mysql,UID/GID多为999
chown -R 999:999 /opt/mysql8/data /opt/mysql8/logs || chown -R mysql:mysql /opt/mysql8/data /opt/mysql8/logs

修正/升级my.cnf

在之前的配置文件中,有一些需要改动的地方,最后的配置如下:

# 建议放到容器内:/etc/mysql/conf.d/zzz-custom.cnf
# (官方镜像会自动加载 /etc/mysql/conf.d/*.cnf)

[client]
# 客户端字符集(mysql 8 OK)
default-character-set = utf8mb4

[mysql]
# 这是 mysql CLI 客户端段;server 监听地址与此无关
default-character-set = utf8mb4

[mysqld]
# 在容器里其实默认就监听全部地址;需要显式则保留,否则可删除
bind-address = 0.0.0.0
explicit_defaults_for_timestamp=ON
# 服务器字符集与排序规则(8.0+ 推荐)
character-set-server = utf8mb4
# 若你是全新 8.0 实例,用新的 0900 排序更合理;
# 若从 5.7 数据迁移且要最大兼容,可先用 utf8mb4_general_ci
collation-server      = utf8mb4_0900_ai_ci

# ⚠️ 只能在“初始化数据目录之前”设置,并且跨平台必须保持一致
# (Linux 上 l_c_t_n=1 需要一开始就这么初始化;已有数据时不可随改)
lower_case_table_names = 1

# (可选)将错误日志写到文件(默认是容器 stdout)
# log-error = /var/log/mysql/error.log

上面的配置主要是把字符集全面升级到了utf8mb4,还添加了一些mysql8新的配置,如explicit_defaults_for_timestamp

启动 MySQL 8 容器指向这三套新目录

在启动容器之前,要确保我们的cpu架构和镜像的适配平台保持一致,比如我的cpu架构是aarch64(ARM64),则我在拉取镜像时可以加上--platform linux/arm64。 方式一:

docker run -d --name mysql8 \
  -e MYSQL_ROOT_PASSWORD=YourStrong!Pass \
  -p 3306:3306 \
  -v /opt/mysql8/data:/var/lib/mysql \
  -v /opt/mysql8/conf:/etc/mysql/conf.d \
  -v /opt/mysql8/log:/var/log/mysql \
  mysql:8.0.41

说明与注意:

  • 首次启动会执行数据升级(因为 /var/lib/mysql 已存在 5.7 数据)。
  • 观察升级日志:
docker logs mysql8

等看到 ready for connections 之类提示后再进行验证。

如果权限报错(Permission denied 或无法写数据),再 chown -R 999:999 /opt/mysql8/data /opt/mysql8/log 一次。

方式二:docker-compose.yml

services:
  mysql:
    image: mysql:8.0
    container_name: mysql8
    environment:
      # 旧数据目录不会用到这个变量,但保留无害
      MYSQL_ROOT_PASSWORD: "YourStrong!Pass"
    ports:
      - "3306:3306"
    volumes:
      - /opt/mysql8/data:/var/lib/mysql
      - /opt/mysql8/conf:/etc/mysql/conf.d
      - /opt/mysql8/log:/var/log/mysql
    # 可选:显式参数(与你的 my.cnf 一致即可)
    command:
      - "--character-set-server=utf8mb4"
      - "--collation-server=utf8mb4_0900_ai_ci"
      - "--lower_case_table_names=1"

启动后验证与常见兼容项

  1. 登录与版本
docker exec -it mysql8 mysql -uroot -p -e "SELECT VERSION();"
  1. 认证插件兼容 如果你的应用驱动较老(不支持 caching_sha2_password),建议为业务账号切换插件(root 不必):
ALTER USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY 'StrongPass!';
FLUSH PRIVILEGES;
  1. SQL 模式/排序规则差异
  • 8.0 的 ONLY_FULL_GROUP_BY 更严格,旧 SQL 可能报错。可先临时放宽(不建议长期):
SET PERSIST sql_mode = REPLACE(@@GLOBAL.sql_mode, 'ONLY_FULL_GROUP_BY', '');
  • 如果排序结果与 5.7 不同,考虑把库/表/会话的排序规则统一到 utf8mb4_general_ci。

回滚策略

  • 现在升级策略是 “拷贝目录–>在拷贝上升级”,若8.0的效果不如预期,则可以删除8.0的容器,重新启动5.7的容器版本,完全不受影响。
  • 切忌把8.0的数据目录指到5.7的版本上,否则原始目录的格式可能也会升级,会滚的难度将变大。

常见问题

  1. 如果不是“干净”关机的话,升级8.0以后可能mysql8.0的容器会起不来,查看日志如下:
docker logs mysql8
2025-09-03 06:55:34+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.6-1.el9 started.
2025-09-03 06:55:35+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2025-09-03 06:55:35+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.4.6-1.el9 started.
'/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
2025-09-03T06:55:36.239957Z 0 [System] [MY-015015] [Server] MySQL Server - start.
2025-09-03T06:55:36.397324Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.4.6) starting as process 1
2025-09-03T06:55:36.432747Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started.
2025-09-03T06:55:36.586776Z 1 [ERROR] [MY-012526] [InnoDB] Upgrade is not supported after a crash or shutdown with innodb_fast_shutdown = 2. This redo log was created with MySQL 5.7.42, and it appears logically non empty. Please follow the instructions at http://dev.mysql.com/doc/refman/8.4/en/upgrading.html
2025-09-03T06:55:36.586857Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
2025-09-03T06:55:37.053175Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
2025-09-03T06:55:37.055603Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
2025-09-03T06:55:37.055726Z 0 [ERROR] [MY-010119] [Server] Aborting
2025-09-03T06:55:37.070429Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.4.6)  MySQL Community Server - GPL.
2025-09-03T06:55:37.070454Z 0 [System] [MY-015016] [Server] MySQL Server - end.

这是**就地升级(沿用 5.7 的数据目录)**典型报错: Upgrade is not supported after a crash or shutdown with innodb_fast_shutdown = 2 … redo log was created with MySQL 5.7.x 意思是:5.7 当时不是“干净关机”,redo 里还有未检查点的数据(而且 fast_shutdown=2)。在这种状态下,8.0 拒绝接手升级。

解决方案上面说了,就是进行“干净关机”的部分。

  1. 另一种迁移的方式–逻辑迁移 这种方式可以绕开 redo/字典升级风险。
    1. 起一个新的空数据目录的 MySQL 8.4 容器(不要挂现在的 ~/docker/mysql/data)。
    2. 在还能启动的 5.7 容器上用 mysqldump 导出,再导入到 8.4。
# 在 5.7 上
mysqldump -uroot -p \
  --single-transaction --routines --triggers --events --all-databases \
  > /backup/all.sql

# 在 8.4 上
mysql -uroot -p < /backup/all.sql    

但是我在尝试这种方式的时候失败了,原因是mysql配置表数据格式不兼容。

在上面第一步产生的all.sql是在容器内部,需要用以下的命令复制出来:

docker cp mysql57:/backup/all.sql ~/all.sql

总结

其实很多问题就是没有找到正确的道路,遇到很多问题的根本原因就是一开始的方向就错了(比如我一开始选版本的时候:)。

Licensed under CC BY-NC-SA 4.0
发表了8篇文章 · 总计6.64k字
Built with Hugo
Theme Stack designed by Jimmy