MySQL 升到 8.4 前的兼容性自检清单

0 阅读7分钟

MySQL 8.0 在 2026 年 4 月 30 日 的支持停了,最后一个可用版本号是 8.0.46。

8.0 是 2018 年 4 月 19 日 GA 的,一路服役了八年。很多生产库现在还跑着它。大部分人第一次听说要升级,是在一份安全扫描报告里。

升级教程到处都是,讲怎么升的居多。少有人讲升之前该查什么。

真正麻烦的地方在于,8.4 有一批变更不给兼容期。有些在 8.0 里只是个 Warning,到 8.4 变 Error。有些建表语句在 8.0 里能过,在 8.4 里直接被拒。而且这些不会在启动时提醒你,只有执行到那一条才暴露。

先说这次的机器。云服务器,4 核 8G,跟 Web 服务共用一台。数据库是 MySQL 8.4.11,容器镜像 mysql:8.4.11。1Panel 容器化部署,端口只映射到 127.0.0.1:3306,不暴露公网。要升的是 8.0.x 社区版。应用侧是 PHP 8.4 加 ThinkPHP 8,走 PDO 连接。

命令里用变量代替容器名,先在终端跑一次:

# 换成你在 1Panel 里给 MySQL 起的名字(= 容器名)
export MYSQL_CTN="mysql84"

标「实测」的结果来自我这次部署,2026 年 9 月 16 日和 17 日,原始回显原样保留。标「官方」的来自 MySQL 官方文档和发布说明,出处随文给出。

8.0 的 GA 是 2018 年 4 月 19 日,支持终止 2026 年 4 月 30 日,已经 EOL。8.4 LTS 的 GA 是 2024 年 4 月 30 日,支持到 2032 年 4 月 30 日,还在支持期内。9.7 LTS 更新一些,GA 是 2026 年 4 月 21 日,支持到 2034 年 4 月 21 日。

EOL 意味着三件事同时停。不再有安全补丁。不再有 bugfix 版本,8.0.46 是最后一个构建。不再有支持案例。

MySQL 官方 8.0 发布说明页的第一段就写着这段原文:

As of April 2026, with version 8.0.46, MySQL 8.0 reaches End of Life (EoL). MySQL 8.0 users are encouraged to upgrade to the latest MySQL 8.4 LTS or MySQL Innovation release.

为什么不直接上 9.7?因为它 2026 年 4 月才 GA,驱动、ORM、运维工具、云厂商托管这些生态都还在跟进。8.4 是从 8.0 出发的原地升级路径,官方明确支持,风险面最小。

所以 8.0 到 8.4 是眼下最稳的一步。但这一步得先过下面这道关。

8.4 有五条变更值得单独盯。

第一条,非标准外键。MySQL 官方 8.4 发布说明原文:

The use of non-unique or partial keys as foreign keys is deprecated in MySQL. Beginning with this release, you must explicitly enable such nonstandard keys … restrict_fk_on_non_standard_key … is ON by default. This means that any attempt to use a nonstandard key as a foreign key in a CREATE TABLE or ALTER TABLE statement is rejected with the error ER_FK_NO_INDEX_PARENT.

翻译过来就是,外键引用的父表列必须有唯一键或者主键。8.0 时代父表上随便一个普通索引也能建外键,8.4 默认不许了。

这里有个容易误读的地方,官方文档专门澄清过。我一开始以为升级那一刻就会炸,其实升级本身不受影响。8.0 库里已经存在的非标准外键可以带过来,服务器在升级时会打一串警告,把它们的名字列出来。被拒的是新建这种外键。

所以风险不在升级那一刻,在升级之后。某天你加一张新表,顺手写了个老习惯的外键,建表直接失败。

第二条,FLOAT 和 DOUBLE 上的 AUTO_INCREMENT。Percona 的 8.0 与 8.4 默认值对照里写得很直白。8.0 是 deprecated with warnings,能用,但报废弃警告。8.4 是 completely removed,直接报错。

一条 SQL 就能扫完:

SELECT table_schema, table_name, column_name, data_type
FROM information_schema.columns
WHERE extra LIKE '%auto_increment%'
  AND data_type IN ('float', 'double');

空结果才说明安全。有结果的话,升级前把那些列改成整数类型,比如 BIGINT。

第三条,新增保留字。8.4 加了一批保留字,MANUAL、PARALLEL、QUALIFY、TABLESAMPLE 都在里面。你要是把其中任何一个当成没加引号的表名或列名,查询直接语法错误。

这条最阴的地方在于,升级当下它不报。等升级之后某条 SQL 突然跑不通,报的还是语法错误,看着像代码被人改坏了。

自检就是把这几个词在表名和列名里搜一遍:

SELECT table_name, column_name
FROM information_schema.columns
WHERE column_name IN ('manual', 'parallel', 'qualify', 'tablesample');

有命中就把标识符用反引号包起来,或者改名。

第四条,int(11) 这类显示宽度写法。int(11)、bigint(20) 里括号里的数字是显示宽度,跟存储范围没关系。这个写法从 8.0.17 起就被标记成废弃。

它不会导致升级失败,目前还只是警告。但既然要动一次数据库,顺手把新表的写法改掉是划算的。新建表直接写 int 或 bigint,不写宽度。

判断依据也简单。这类写法只出现在你手写的 DDL 里。ORM 生成的迁移脚本、新版本工具导出的结构,默认都不带。

第五条,认证插件。8.4 沿用了 8.0 的默认插件 caching_sha2_password。但它更进一步,把 mysql_native_password 默认禁掉了。这个插件在 8.0.34 就标了废弃,9.0 会彻底移除。

直接后果是老客户端连不上。比如 Navicat 11 及更早版本会报:

Authentication plugin 'caching_sha2_password' cannot be loaded

正确的做法是升级客户端,Navicat 16+ 或者 DBeaver,而不是把认证插件改回去。改回去只是把问题推到 9.0。

应用侧不用慌。PHP 的 PDO 和 mysqli 从 7.4.4 起就支持 caching_sha2_password。只要你的 PHP 不低于 7.4.4,代码不用动。

这五条里,FLOAT 和 DOUBLE 那条、保留字那条是纯 SQL。外键和显示宽度要扫 DDL 文本,认证插件查客户端版本。

扫 DDL 文本用正则就够,不用上工具。关键是扫的对象要全,不只是建表脚本,还要包括所有 ALTER TABLE 和迁移文件:

# ① 外键:找出所有 FOREIGN KEY 定义,人工过一眼有没有引用非唯一键
grep -rniE "foreign key" --include="*.sql" .

# ② 显示宽度:int(11) / bigint(20) 这类写法
grep -rnE "\b(int|bigint|tinyint|smallint|mediumint)\([0-9]+\)" --include="*.sql" .

# ③ 新增保留字当标识符用(反引号包住的不算问题)
grep -rniE "(^|[^`a-z_])(manual|parallel|qualify|tablesample)([^`a-z_]|$)" --include="*.sql" .

三条命令的判据统一。空结果就是没有要处理的地方。有结果不等于有问题,但每一条都值得看一眼。

我这套要升的库,把五条全扫了一遍。这张清单我当时是照着一条条跑的。

非标准外键 0 处,全库外键数量本来就是 0。FLOAT 和 DOUBLE 加 AUTO_INCREMENT 0 处。8.4 新增保留字冲突 0 处。int(11) 显示宽度写法 0 处。排序规则声明 31 处,全部是 utf8mb4_unicode_ci,8.4 完整支持。PDO 驱动版本是 PHP 8.4.25,远高于 7.4.4,支持 caching_sha2_password。

结论是脚本零改造,直接上 8.4。

这里最值得说一句的是外键数量为 0。这不是运气。它同时说明两件事。一是不受非标准外键那条新规则影响。二是这个库从一开始就没打算靠数据库层保证引用完整性,这本身是个取舍,不是这篇的主题。

另外那 31 处 utf8mb4_unicode_ci 有个隐藏前提。8.4 的 utf8mb4 默认排序规则是 utf8mb4_0900_ai_ci。我的脚本里写的是 utf8mb4_unicode_ci。建库时如果留空排序规则,库级会拿到那个新默认值。它跟表级的 utf8mb4_unicode_ci 混在一起,跨表 JOIN 做字符串比较就会报:

Illegal mix of collations (utf8mb4_0900_ai_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation '='

所以建库那一项必须显式选,不能靠默认值。