不少朋友手里还跑着 MySQL 5.7 的老项目,最近因为业务需求开始琢磨 MySQL 8.0。有人眼馋新功能,有人担心升级后 SQL 跑不动,还有人连安装都卡在初始化那一步。作为一个把 MySQL 8.0 从测试环境一路用到生产环境的人,我想把这段时间积累下来的东西做个系统梳理:8.0 到底改了哪些底层逻辑,Linux 和 Docker 两种部署方式怎么选,以及从 5.7 升上来会遇到哪些坑。这篇内容更适合后端开发、DBA 和准备升级数据库的技术团队,读完你至少能对 8.0 有一个完整判断,并且能照着文章把环境搭起来。
1. 内容整体设计与思路拆解
1.1 从 5.7 到 8.0:一次等得够久的升级
MySQL 5.7 算得上是一代经典,从 2015 年发布到 8.0 正式登场,中间隔了好几年。这段时间里业界需求其实发生了很大变化:数据量级从 GB 涨到 TB,业务 SQL 越来越复杂,安全合规要求越来越高,JSON 这类半结构化数据也越来越常见。5.7 的设计底子还在 2000 年代,很多问题靠打补丁已经补不动了。
8.0 不是一个常规的“5.7 加几个功能”的小版本,而是一次底层重构。官方把数据字典从 MyISAM 搬到了 InnoDB,把 DDL 变成了原子操作,把默认字符集换成了 utf8mb4,把默认认证插件换成了 caching_sha2_password。这些改动表面看是“换了个默认值”,实际上是把过去十年里积累的技术债一次性还清。
我在帮团队做技术选型的时候,对 8.0 的判断是:如果你还在用 5.6 或更早的版本,升级基本没有犹豫空间;如果你在用 5.7,可以等兼容性验证做完再上路。但方向上一定是从 5.7 走向 8.0,而不是停留在老版本。原因很简单,8.0 之后的迭代节奏明显加快,很多新特性和性能优化都只出现在 8.0 这条线上。
1.2 8.0 的设计关键词:原子性、SQL 标准、感知资源
看 8.0 的官方 Release Notes,特性很多,但背后有三条主线。
第一条是原子性。以前执行一条 ALTER TABLE,如果执行到一半数据库崩溃,重启后你可能得到一个中间状态的表:有的行改了,有的行没改,甚至数据字典和表定义不一致。8.0 把元数据全部集中在 InnoDB 的数据字典里,配合 redo log 和 undo log,让 DDL 也变得原子。这对自动化运维和灰度发布意义重大,脚本里跑 DDL 不再需要担心“跑到一半挂了怎么回滚”。
第二条是更贴近 SQL 标准。窗口函数、公共表表达式(CTE)、CHECK 约束这些能力,让复杂报表和分析查询可以写出更优雅的 SQL,而不是靠临时表或者自连接硬凑。以前这类需求通常要拿到应用层用代码处理,现在数据库内部就能解决,传输和开发成本都降下来了。
第三条是感知资源。8.0 在优化器里加入了直方图、哈希连接、自适应参数等能力,让优化器对数据分布和内存资源更敏感。再加上复制层面支持 WriteSet 并行复制,多核机器的吞吐能力被进一步释放。这些都是实打实的性能收益,不是只在基准测试里好看。
1.3 谁该升级,谁可以先观望
不是说所有人都要马上动。我个人的建议分三种情况:
新项目,直接上 8.0,没必要再开 5.7 的新库。
老项目且版本是 5.7,认真做一轮兼容性评估,重点看 SQL 写法、客户端驱动、字符集和 sql_mode 几个维度,评估通过就抓紧升。
老项目还在 5.5 或 5.6,建议不要跨版本硬跳,先升到 5.7 再升 8.0,或者用逻辑导出导入的方式重建实例,避免一次跨太多版本带来不可控风险。
我见过太多团队因为“不敢动”一直拖,最后被安全漏洞和性能瓶颈逼着做紧急升级,那种状态下的风险反而更大。8.0 是那个值得主动拥抱的版本。
2. 八个值得反复琢磨的新特性
2.1 数据字典与原子 DDL
这是 8.0 最底层、影响最深远的一个改动。
在 5.7 及之前,MySQL 的元数据分散在很多地方:表的定义放在 .frm 文件里,权限信息在 mysql 库的 MyISAM 表里,部分状态信息还在内存中。这种分散架构带来的问题很典型:一条 DDL 要更新文件、系统表、缓存等多个位置,任何一个环节出错都可能导致不一致。崩溃恢复时,表文件还在但字典信息丢了的情况并不罕见。
8.0 把所有元数据统一放进了 InnoDB 表,存储在数据目录的 mysql.ibd 文件中。好处很直接:
- 元数据读取和写入具备事务性,崩溃恢复后字典信息依然一致;
- 不再有 frm 文件,表定义和数据文件强关联;
- DDL 操作本身变成原子操作,要么成功,要么完整回滚。
我用一个生活化的类比来理解这件事:5.7 的元数据管理就像一家小公司用手工台账记录客户信息,员工离职、台账被咖啡泼了,信息就乱了;8.0 则是上了 ERP 系统,所有记录写入同一个带事务的数据库,每一步都有日志。后者虽然在系统初始化时更复杂,但长期稳定性完全不在一个量级。
原子 DDL 带来的实际体验提升很明显。以前对一个大表执行在线加列,如果磁盘被写满导致失败,表可能变成“可用但状态异常”。8.0 里失败后一切回到原样,你只需要解决磁盘空间再重试就行了。
2.2 窗口函数和CTE:报表SQL的降维打击
这是 SQL 开发者感受最直观的变化。
先看一个场景:你要统计每个部门薪资排名前 3 的员工。5.7 时代常见做法是使用用户变量:
SELECT dept_no, emp_no, salary, @rn := IF(@prev_dept = dept_no, @rn + 1, 1) AS rn, @prev_dept := dept_no FROM ( SELECT dept_no, emp_no, salary FROM employee ORDER BY dept_no, salary DESC ) t CROSS JOIN (SELECT @prev_dept := NULL, @rn := 0) vars HAVING rn <= 3;这个写法看着就绕,而且用户变量的执行顺序在不同版本里有过差异,实际跑线上 SQL 总有点心里没底。
8.0 里直接用窗口函数:
SELECT dept_no, emp_no, salary, rn FROM ( SELECT dept_no, emp_no, salary, ROW_NUMBER() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS rn FROM employee ) t WHERE rn <= 3;语义一目了然。常用的 ROW_NUMBER、RANK、DENSE_RANK、LEAD、LAG、SUM/AVG 加 OVER 子句,几乎覆盖了各类统计需求。
CTE 则解决了多层嵌套子查询的可读性问题。以前写多步中间结果只能一层套一层,现在可以先用 WITH 定义临时数据集,再逐步组合:
WITH dept_total AS ( SELECT dept_no, SUM(salary) AS total_salary FROM employee GROUP BY dept_no ), top_dept AS ( SELECT dept_no FROM dept_total ORDER BY total_salary DESC LIMIT 3 ) SELECT e.emp_no, e.name, e.salary FROM employee e JOIN top_dept td ON e.dept_no = td.dept_no;基于这两类语法,很多原本靠程序代码才能完成的统计分析,现在一条 SQL 就能搞定。开发同学可以减少一次数据传输和逻辑处理,查询性能反而更好。
2.3 默认字符集更换为 utf8mb4
8.0 的默认字符集从 latin1 换成了 utf8mb4,默认排序规则是 utf8mb4_0900_ai_ci。这一步的意义比表面看起来大得多。
以前 5.7 建库如果不显式指定字符集,默认是 latin1。这个字符集连中文都存不了,更别说 emoji 和特殊符号。所以早期项目里经常出现“数据库是 latin1,表是 utf8,连接参数又是 gbk”的混乱局面,乱码问题按下葫芦浮起瓢。
8.0 把默认值改成 utf8mb4,意味着新建的库、表、字段默认就能支持所有 Unicode 字符。从源头减少了乱码问题的发生概率。
这里有个容易踩的细节:utf8mb4_0900_ai_ci 是 Unicode 9.0 的排序规则,和 5.7 时代常用的 utf8mb4_general_ci 或 utf8mb4_unicode_ci 在排序比较行为上不完全一样。比如某些特殊字符的排序权重,两者可能不同。如果你从 5.7 迁移数据,库表字符集都是 utf8mb4,但排序规则变了,可能导致 ORDER BY 的结果顺序出现差异。迁移前一定要检查所有字段的 collation,不能只看字符集。
2.4 索引层的两个细节:隐形索引与降序索引
索引方面的改进看起来小,实际价值很高。
隐形索引允许你把索引设为对优化器不可见,但保留索引定义。以前要评估“删掉某个索引后性能会不会变差”,只能真的删掉,跑一遍验证,有问题再重建。对大表来说,删索引很快,重建索引却可能花上几个小时。现在可以先把索引设为 INVISIBLE,观察一段时间业务表现,没问题再删,有问题一条 SQL 就能恢复可见性。
ALTER TABLE employee ALTER INDEX idx_dep_salary INVISIBLE; ALTER TABLE employee ALTER INDEX idx_dep_salary VISIBLE;降序索引则解决了多列排序方向不一致时的性能痛点。8.0 之前,索引列虽然也能指定 DESC,但实际存储时是按升序处理的。如果查询是 ORDER BY col1 ASC, col2 DESC,优化器往往需要额外排序。8.0 真正实现了降序存储,可以严格按索引顺序扫描,避免 filesort。
这个特性对综合排序、排行榜这类业务很有帮助。我在一个排行榜需求里,把索引从(score, update_time)改成(score DESC, update_time ASC)后,查询时间从几十毫秒降到了个位数毫秒,收益非常明显。
2.5 JSON 能力补强:从存储扩展为计算
5.7 已经支持 JSON 类型,但功能比较基础。8.0 在这个基础上补了很多实用功能,最关键是 JSON_TABLE 函数。
JSON_TABLE 可以把 JSON 数组展开成关系表,然后和普通表做 JOIN 或聚合查询。这类需求在过去开发里很常见:业务在 MySQL 里存了一些 JSON 配置,又要按其中的某个字段做统计。以前只能取出整个 JSON 在应用层解析,现在可以在 SQL 内部完成。
SELECT jt.id, jt.name, jt.score FROM player_tags, JSON_TABLE(player_tags.tags, '$[*]' COLUMNS ( id INT PATH '$.id', name VARCHAR(50) PATH '$.name', score DECIMAL(5,2) PATH '$.score' ) ) AS jt;还有 JSON_OVERLAPS 判断两个 JSON 数组是否有交集、JSON_VALUE 从 JSON 中抽取值并转换为 SQL 类型等功能。对于轻量级半结构化数据场景,8.0 的 JSON 能力基本可以替代一部分 NoSQL 的活,让团队少维护一套存储。
不过我不建议把 MySQL 当 MongoDB 用,大量复杂 JSON 嵌套查询还是会力不从心。JSON 列适合“存结构化数据里的少量弹性字段”,不适合“整表都用 JSON 存业务逻辑”。
2.6 角色与权限体系完善
8.0 正式引入了角色(Role)概念,等于把常用权限集合做成了可以复用的“权限包”。
以前要给几十个新员工开通只读权限,要么逐个 GRANT,要么写一堆脚本。现在先定义一个角色:
CREATE ROLE 'readonly'; GRANT SELECT ON mydb.* TO 'readonly'; GRANT 'readonly' TO 'user1', 'user2', 'user3';后续要收回权限,也只要改角色本身,所有继承该角色的用户自动生效。这改变了权限管理的粒度,从用户级别提升到了角色级别。
同时 8.0 的权限目录比 5.7 多了不少细项,比如可以单独控制备份锁权限 BACKUP_ADMIN、复制客户端权限 CLONE_ADMIN 等。配合前面提到的数据字典事务性,权限变更操作也更可靠。
2.7 性能优化器:哈希连接与直方图
先说哈希连接。8.0 的优化器会在合适的场景下自动选择哈希连接算法,特别是大表和等值连接场景,比传统的嵌套循环连接快很多。这个特性不需要手动开关,优化器自动判断,实测中复杂关联查询的提升非常明显。
再说直方图。以前优化器估算某列的过滤效果,主要靠索引统计信息。如果条件列没有索引,优化器只能猜一个比例,一旦猜错,执行计划就会跑偏。8.0 可以手动收集直方图:
ANALYZE TABLE employee UPDATE HISTOGRAM ON dept_no, salary;直方图会记录列数据的分布情况,让优化器对“SELECT * FROM t WHERE col = '某个不常出现的值'”这类查询的估算更准确。对无索引列的等值查询、范围查询都有帮助。
我实际遇到过一个性能问题:一张订单表在状态字段上没有索引,业务查询按 status 过滤,status 有几个值,但分布极端不均匀。优化器总是以为每个值都能过滤掉 90% 的数据,实际却返回了 60% 的行。收集直方图之后,执行计划立刻变合理,查询时间降了一个数量级。
2.8 安全与一致性层面的几处硬改进
8.0 在安全方面的动作不止换认证插件那么简单。
第一,默认认证插件改为 caching_sha2_password,它比老的 mysql_native_password 加密强度更高,并且支持服务端和客户端之间的密码安全传输。代价是老的客户端驱动如果不更新,可能会报插件不兼容,这一点后面迁移部分详细说。
第二,8.0 对密码策略有了更多默认限制,比如默认启用密码校验组件,简单密码在创建用户时会被拒绝。
第三,通用日志和慢查询日志也发生了变化,日志表改用了 InnoDB 存储引擎,写入一致性更好。
第四,支持双重密码机制。你可以在保留旧密码的同时设置一个新密码,让旧客户端逐步切换,切换完成后再丢弃旧密码。对于大规模系统做滚动升级,这个功能太实用了。
ALTER USER 'app_user'@'%' IDENTIFIED BY 'new_password' RETAIN CURRENT PASSWORD;这些改进单个看可能不起眼,但组合起来让 8.0 在等保、等合规场景里更容易通过检查和审计要求。
3. 实操:Linux 下从零安装 MySQL 8.0
3.1 安装前的环境准备
Linux 下安装 MySQL 8.0,我推荐直接使用官方 Generic Linux 二进制包,也就是 tar.xz 方式,不依赖系统发行版的包管理器,可控性更强。当然你用 yum 或 apt 也可以,但版本和路径可能被系统仓库限制。
先下载安装包:
wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz解压到 /usr/local:
tar -xvf mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz -C /usr/local cd /usr/local ln -s mysql-8.0.32-linux-glibc2.12-x86_64 mysql创建 MySQL 系统用户:
groupadd mysql useradd -r -g mysql -s /sbin/nologin mysqlMySQL 8.0 的二进制包默认动态链接了一些依赖库,常见的有 libaio、numactl。CentOS 环境先装一下:
yum install -y libaio numactl如果缺 libaio,启动时可能报error while loading shared libraries,这个坑我踩过一次,白折腾了半天才定位到。
创建数据目录:
mkdir -p /data/mysql chown -R mysql:mysql /data/mysql3.2 解压初始化与启动
8.0 初始化数据库不再使用 mysql_install_db 这个命令,而是直接用 mysqld --initialize。
先写一个最小可用的 /etc/my.cnf:
[mysqld] basedir=/usr/local/mysql datadir=/data/mysql socket=/tmp/mysql.sock pid-file=/data/mysql/mysql.pid log-error=/data/mysql/error.log port=3306然后初始化:
/usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql初始化过程会自动生成一个临时 root 密码,输出在错误日志里。我一开始不知道这个细节,初始化后直接登数据库,被拒绝了好几次。查看日志的方式:
grep "temporary password" /data/mysql/error.log日志里类似这样:
[Note] A temporary password is generated for root@localhost: xxxxxxxx拿到临时密码后,启动数据库:
/usr/local/mysql/bin/mysqld --user=mysql &或者用官方推荐的方式,把 mysqld 注册成 systemd 服务。如果一切正常,进程会起来,可以通过 mysql 命令连接:
/usr/local/mysql/bin/mysql -uroot -p登录后第一件事是改密码:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Your-New-Pass123';注意 8.0 默认启用了密码校验组件,简单密码会直接报错,需要设置包含大小写字母、数字和特殊字符的密码。
3.3 基础安全配置和参数推荐
安装完成后,有几个配置我建议立刻动手。
创建远程连接账号。很多业务需要从应用服务器远程连库,root 账号默认只允许 localhost,建议单独建账号并授权:
CREATE USER 'app'@'%' IDENTIFIED BY 'StrongPass123'; GRANT ALL PRIVILEGES ON mydb.* TO 'app'@'%'; FLUSH PRIVILEGES;调整 my.cnf 核心参数。下面是一份适合 8G 内存、中等业务量的推荐配置:
[mysqld] innodb_buffer_pool_size = 4G innodb_log_file_size = 512M max_connections = 300 slow_query_log = 1 slow_query_log_file = /data/mysql/slow.log long_query_time = 1innodb_buffer_pool_size 建议设置为物理内存的 50% 到 70%,这是 InnoDB 最重要的缓存参数。max_connections 不要盲目调大,连接数越多,线程上下文切换开销越大,300 到 500 之间对大多数业务足够。
注意:8.0 中 my.cnf 里要避免配置 query_cache_size 这类参数,查询缓存已经在 8.0 中移除了。如果从 5.7 的配置文件直接复制,会看到启动失败或者警告。
3.4 踩过的坑:初始化、目录权限、systemd 注册
第一个坑是初始化报错[ERROR] --initialize specified but the data directory has files in it。这是因为 /data/mysql 目录不为空。清空目录再初始化即可,或者换一个全新目录。
第二个坑是启动后连不上,报Can't connect to local MySQL server through socket '/tmp/mysql.sock'。多数情况是 mysqld 没起来,优先看 /data/mysql/error.log,不要反复重启浪费时间。常见原因包括目录权限不对、内存不够、端口被占用。
第三个坑是 systemd 注册。如果你用 service mysqld start 发现不识别,可以写专用 unit 文件:
[Unit] Description=MySQL Server 8.0 After=network.target [Service] User=mysql Group=mysql Type=forking ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf ExecStop=/usr/local/mysql/bin/mysqladmin -uroot -p shutdown PIDFile=/data/mysql/mysql.pid Restart=on-failure [Install] WantedBy=multi-user.target填入 /etc/systemd/system/mysqld.service 后执行:
systemctl daemon-reload systemctl enable mysqld systemctl start mysqld生产环境不建议用裸进程方式启动,systemd 接管守护、自愈、日志收集都要方便得多。
4. 实操:Docker 方式安装并使用 MySQL 8.0
4.1 为什么我会推荐 Docker 方案
开发环境和测试环境,我现在更倾向用 Docker 跑 MySQL。原因很简单:隔离干净、销毁方便、版本切换成本低。
一台机器上可能同时需要 5.7 和 8.0,容器化可以直接解决端口冲突和依赖冲突。跑一个测试用例,起一个容器,用完直接删,不会污染宿主机环境。对于本地开发来说,这是体验提升最大的一种方式。
另外,Docker 官方镜像的 MySQL 8.0 是基于 Oracle 官方发布版构建的,行为和二进制安装没有本质区别,配置语法也一致。调试时可以用 docker exec 进入容器,里面也有完整的 mysql 客户端和工具链。
4.2 一条 docker run 跑起来
先拉取镜像:
docker pull mysql:8.0注意这个标签实际指向 8.0.x 的最新版,比如 8.0.32。如果你需要精确版本,可以拉mysql:8.0.32。
启动一个最简容器:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPass123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0解释一下参数:
- -d 后台运行;
- --name 容器名;
- -p 3306:3306 映射宿主机端口到容器内部端口;
- -e MYSQL_ROOT_PASSWORD 设置 root 密码;
- -v mysql-data:/var/lib/mysql 把数据目录挂到命名卷里,容器删除后数据不丢。
启动完成后,在宿主机上直接连接:
mysql -h127.0.0.1 -P3306 -uroot -p如果宿主机没有安装 mysql 客户端,也可以进入容器操作:
docker exec -it mysql8 mysql -uroot -p4.3 数据卷、配置与时区
数据卷是最容易犯错的环节。如果不挂载数据卷,容器一旦被删,所有表数据、binlog、undo log 全部消失,这不是“配置丢了”那么简单,而是整个数据库都没了。生产环境使用 Docker 跑 MySQL 时,数据卷和配置文件都要显式挂载。
配置文件挂载方式:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPass123 \ -v /opt/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf \ -v /opt/mysql/data:/var/lib/mysql \ -v /opt/mysql/logs:/var/log/mysql \ mysql:8.0官方镜像默认会读取 /etc/mysql/conf.d 下的所有 .cnf 文件,所以宿主机的自定义配置可以放在挂载文件里,不用覆盖镜像默认配置。
时区问题也需要处理。容器默认 UTC 时区,日志时间会比北京时间慢 8 小时,查问题的时候对不上时间很痛苦。可以在环境变量里加TZ=Asia/Shanghai,也可以启动后执行:
SET GLOBAL time_zone = '+08:00';最稳妥的是在 my.cnf 中写:
[mysqld] default-time-zone = '+08:00'顺便把字符集也固定住,使用可重复执行的启动命令:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPass123 \ -e TZ=Asia/Shanghai \ -v /etc/localtime:/etc/localtime:ro \ -v mysql-data:/var/lib/mysql \ -v /opt/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_0900_ai_ci4.4 与宿主机文件的互通和备份
容器里生成的 SQL 备份文件默认在容器文件系统里,宿主机器直接抓不到。备份时可以用两种方式。
方式一,在宿主机执行命令,把输出重定向到宿主机文件:
docker exec mysql8 sh -c 'exec mysqldump -uroot -pYourPass123 mydb' > /opt/backup/mydb.sql方式二,先把备份文件 dump 到容器内,再用 docker cp 拷出来:
docker exec mysql8 mysqldump -uroot -pYourPass123 mydb > /tmp/mydb.sql docker cp mysql8:/tmp/mydb.sql /opt/backup/恢复时反向操作:
docker exec -i mysql8 mysql -uroot -pYourPass123 mydb < /opt/backup/mydb.sql用 Docker 跑 MySQL 还有一个隐藏优点:binlog 和慢日志也可以挂载出来,或者直接在宿主机日志目录查看,排障效率明显提升。
不过我也要说清楚,Docker 适合开发、测试和中小型业务,大规模生产库我还是建议走裸机或云托管方案,毕竟 I/O 性能和运维成熟度更好。到底选哪种,取决于你的团队规模和对基础设施的掌控能力。
5. 从 5.7 平滑迁移到 8.0 的排雷清单
5.1 认证插件是第一个翻车点
8.0 默认认证插件是 caching_sha2_password,而 5.7 默认是 mysql_native_password。如果你的一些老客户端驱动版本太低,连接到 8.0 时会直接报错:
Authentication plugin 'caching_sha2_password' cannot be loaded不要急着怪 MySQL,这是驱动跟不上新协议的典型表现。解决办法有两个方向。
第一个方向是升级客户端驱动。Java 的 mysql-connector-java 5.1.x 基本不支持 caching_sha2_password,需要升级到 8.0.x 系列;PHP 的 mysqli / PDO_mysql 也需要确认版本。这个方案更推荐,长期来看安全性和兼容性都有保障。
第二个方向是把用户的认证插件改回老的:
ALTER USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'YourPass123';这个方式适合短期过渡,比如旧客户端太多,来不及一次升级完。它不是长久之计,因为 MySQL 官方已经明确老插件在后续版本会逐渐废弃。8.0.27 版本起相关参数开始标记废弃,未来版本会有更严格的限制。
5.2 默认 sql_mode 更严格了
5.7 时代默认的 sql_mode 也包含了一些严格模式,但 8.0 把更多规则纳入默认值,比如:
- NO_ZERO_DATE;
- NO_ZERO_IN_DATE;
- STRICT_TRANS_TABLES;
- ONLY_FULL_GROUP_BY。
这些都是 5.7 里可能没启用或者启用程度不同的规则。迁移后经常出现三种情况:
第一种,插入或更新数据时报Incorrect date value。以前允许写 '0000-00-00',现在直接拒绝。需要排查业务数据里是否有零日期。
第二种,GROUP BY 查询报错。以前允许SELECT name, salary FROM emp GROUP BY dept_no这种不严谨写法,现在被 ONLY_FULL_GROUP_BY 拦住了。需要改写 SQL,把所有非聚合列都放进 GROUP BY 或用聚合函数包起来。
第三种,字符串截断时以前可能只告警,现在直接报错。比如 varchar(10) 插入超过 10 个字符的字符串,现在会插入失败。
迁移前用下面命令把新旧 sql_mode 对比出来:
-- 5.7 上执行 SELECT @@GLOBAL.sql_mode; -- 8.0 上执行 SELECT @@GLOBAL.sql_mode;把两个值放到文本里 diff 一下,逐条分析差异。如果业务改造工作量太大,临时在 8.0 里改回宽松模式也不是不行,但一定要意识到这是用数据质量换兼容性,不是长久之计。
5.3 从旧版本迁移的具体步骤
我最推荐的方式是逻辑导出导入,适合绝大多数中大型系统,毕竟跨大版本做物理升级风险太高。
第一步,在 5.7 源库导出数据:
mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF \ --databases mydb > mydb_57.sql--single-transaction 保证 InnoDB 表备份时一致性读,不会锁表;--set-gtid-purged=OFF 避免备份文件中包含 GTID 信息与 8.0 冲突。
第二步,在 8.0 目标库导入:
mysql -uroot -p < mydb_57.sql导入前建议先导出空库结构,检查所有建表语句在 8.0 下能否正常执行。因为有些 5.7 支持的数据类型或默认值写法,在 8.0 可能已经变化,比如 non-UTF8 的用法。
第三步,全量比对数据。行数、主键、总数据量都要对比,我习惯用几张关键大表做校验,并用CHECKSUM TABLE做一致性确认。
还有一种做法是使用 MySQL Shell 的升级检查工具。它会在升级前自动检查数据库和对象是否兼容 8.0:
mysqlsh --uri root@127.0.0.1:3306 -- util checkForServerUpgrade这个工具能查出很多人工容易漏掉的问题,比如,不再支持的函数、保留字冲突、不支持的 SQL 语法。强烈建议在迁移前跑一遍。
5.4 升级后性能反而变差的排查点
有时候升级完功能没问题,但个别 SQL 比以前慢,不要慌,大概率不是 8.0 不行,而是执行计划变了。
先看表统计信息是否过期。迁移数据后 ANALYZE TABLE 跑一遍:
ANALYZE TABLE employee;再看 SQL 的 EXPLAIN 输出,对比 5.7 和 8.0 的执行计划差异。8.0 优化器会尝试哈希连接,某些场景下反而会选择和 5.7 不同的连接顺序。如果发现关联顺序不合理,可以用 FORCE INDEX 或调整 join_buffer_size 试探。
还有一个隐蔽点:8.0 默认开启的会话级临时表内存参数 tmp_table_size 和 max_heap_table_size 的默认值比 5.7 大还是小?默认确实调了,但如果你之前的会话参数设置没有带到新配置里,可能影响排序和 GROUP BY 的性能。检查一下这些参数是否一致。
升级后的第一周,建议开启慢查询日志并保留一段 binlog,便于随时复盘。不要急着把旧实例删掉,等业务稳定运行一两周再回收老资源。
6. 常见问题速查与运维建议
6.1 报错:caching_sha2_password cannot be loaded
这个已经说过,核心是客户端驱动版本太老。解决方案优先级依次是升级驱动、调整用户认证插件、修改默认认证配置。不建议为了迁就个别组件而全局降级默认认证插件,那样等于放弃 8.0 的安全升级。
6.2 报错:查询超时或连接不上
连接不上先看端口监听、防火墙和 bind-address。8.0 默认监听所有网卡还是只监听 127.0.0.1,取决于配置文件,一般生产环境要显式设置 bind-address=0.0.0.0 或指定内网 IP。
查询超时则要从执行计划入手。先开慢查询日志,再对慢 SQL 执行 EXPLAIN,看有没有全表扫描、索引失效、临时表过大。8.0 的 EXPLAIN ANALYZE 能输出实际执行时间和行数,比传统 EXPLAIN 更直观:
EXPLAIN ANALYZE SELECT * FROM employee WHERE emp_no = 10001;这个命令会真实执行 SQL,而不是只评估计划,非常实用。
6.3 参数设置要点速查表
整理一份常见参数建议值,方便直接抄作业:
| 参数 | 建议配置 | 说明 |
|---|---|---|
| innodb_buffer_pool_size | 物理内存的 50% ~ 70% | 越大缓存命中率越高 |
| innodb_log_file_size | 256M ~ 1G | 涉及 redo 日志总量,8.0 默认已经调大 |
| max_connections | 按实际线程估算 | 并发高时优先考虑连接池 |
| long_query_time | 1 秒 | 慢查询阈值 |
| slow_query_log | ON | 生产环境必须开启 |
| binlog_format | ROW | 逻辑复制和数据恢复依赖 |
| expire_logs_days | 7 或根据需求 | 8.0 中可用 binlog_expire_logs_seconds 替代 |
| character_set_server | utf8mb4 | 统一字符集,防乱码 |
| collation_server | utf8mb4_0900_ai_ci | 8.0 默认排序规则 |
有一点要注意:修改 innodb_log_file_size 在 8.0 里没有之前那么麻烦,但不能在线直接改,需要重启生效。参数调整层面,建议先小步改动,观察监控指标,确认稳定后再推广。
6.4 个人对 8.0 使用习惯的几点建议
最后说几个我实际用下来的体会。
第一,升级别贪多求快。不要一上来就把所有特性都用上,先把基础跑稳。比如窗口函数虽然好用,但也要确保团队里每个人都能看懂、写好,否则维护成本反而上升。
第二,监控体系要跟上。8.0 和 5.7 的监控指标存在差异,比如查询缓存相关变量没了,redo log 相关视图变了。老监控面板要同步适配,否则数据缺失可能掩盖问题。8.0 的 Performance Schema 有很多新表,值得花时间研究。
第三,分阶段灰度。先在测试环境充分验证,再把从库升级,最后切换主库。如果你用的是主从架构,记得先升级从库观察复制延迟和告警,确认没问题再操作主库。
第四,碰到问题多利用官方工具。MySQL Shell 的升级检查、EXPLAIN ANALYZE、Performance Schema、sys schema 这些综合用起来,排障效率会高很多。很多 5.7 时代靠猜的问题,8.0 时代可以直接用工具定位。
MySQL 8.0 不是那种“装完没感觉”的版本。它从底层字典、SQL 能力、优化器到安全模型都往前迈了一大步。安装和迁移的坑说多不多,但每一个都够让人折腾一晚。我希望这篇整理下来,大家照着部署时能少走一些弯路。后续我还会继续分享 8.0 在高并发场景下的性能调优实践,欢迎一起交流。