背景
因为 MySQL5.7 快要 EOL 了,对于不打算替换为其他国产数据库的用户,就需要研究如何升级 MySQL5.7 到 MySQL8.0 了。
如果不了解 EOL 是什么意思,请考我这篇文章《阴谋论: MySQL 将死,国产数据库的春天?》
实际上,从 MySQL5.7 大版本升级到 MySQL8.0,和 MySQL5.7 小版本升级到 MySQL5.7 最新版本的步骤是差不多。但有一个注意事项还有一个额外步骤我们要知道。
注意事项
注意事项就是如何回退。虽然回退并不常见,但作为升级失败后的保命手段,我们是必须制定方案的。对于 MySQL5.7 小版本升级到 MySQL5.7 版本,我们可以使用与升级相同的步骤:替换回旧的二进制包,并用旧的二进制程序 mysql_upgrade 重新更新元数据,实现回退。然而,MySQL8.0 则不行。一旦升级到 MySQL8.0,无论是从 5.7 升级到 8.0,还是 8.0 小版本之间的升级,都不支持降版本,也就是不支持回退。
因此,在升级前,务必进行全库备份,以便在升级失败时进行回退,并且使用至少两种备份手段去备份,以确保万无一失!
额外步骤
由于 MySQL5.7 小版本之间升级,为了稳,我们也会做物理全备的,所以我说的这个额外步骤,并不是指备份。而是升级之前,需要仔细检查实例是否具备"可升级"的状态。需要做这些检查主要是因为 升级到 MySQL8.0 属于大版本升级,会有一些功能的废弃与修改、语法的废弃与修改、默认值的废弃与修改、字符集的调整、引擎特性的变化、数据类型限制的改动、新引入的关键字、存储过程和函数的兼容性调整等等。而这些是 MySQL5.7 小版本之间升级不需要检查的。
我说的只是升级时的运维视角,而业务视角、开发视角是需要用准生产环境做一次全面测试的,主要是关注 SQL 兼容性等,可以参考陈臣老师的这篇文章。《不可不知的 MySQL 升级利器及 5.7 升级到 8.0 的注意事项》
那么具体需要检查什么,怎么检查?官方提供了基于 mysqlshell 工具集的 util.checkForServerUpgrade 工具。它提供了以下 26 项小检查项。
Usage of old temporal type MySQL 8.0 syntax check for routine-like objects Usage of db objects with names conflicting with new reserved keywords Usage of utf8mb3 charset Table names in the mysql schema conflicting with new tables in 8.0 Partitioned tables using engines with non native partitioning Foreign key constraint names longer than 64 characters Usage of obsolete MAXDB sql_mode flag Usage of obsolete sql_mode flags ENUM/SET column definitions containing elements longer than 255 characters Usage of partitioned tables in shared tablespaces Circular directory references in tablespace data file paths Usage of removed functions Usage of removed GROUP BY ASC/DESC syntax Removed system variables for error logging to the system log configuration Removed system variables System variables with new default values Zero Date, Datetime, and Timestamp values Schema inconsistencies resulting from file removal or corruption Tables recognized by InnoDB that belong to a different engine Issues reported by 'check table x for upgrade' command New default authentication plugin considerations Columns which cannot have default values Check for invalid table names and schema names used in 5.7 Check for orphaned routines in 5.7 Check for deprecated usage of single dollar signs in object names
翻译:
1)旧的时间类型使用
2)类存储过程对象的 MySQL8.0 语法兼容检查
3)与新保留字、新关键字冲突的数据库对象名称使用
4)使用 utf8mb3 字符集
5)mysql 库中的表名与 8.0 中的新表冲突
6)使用非本地分区引擎的分区表
7)外键约束名超过 64 个字符
8)使用过时的 MAXDB sql_mode 标志
9)使用过时的 sql_mode 标志
10)ENUM/SET 列定义包含长度超过 255 个字符的元素
11)在共享表空间中使用分区表
12)表空间数据文件路径中的循环目录引用
13)使用已删除的函数
14)使用已删除的 GROUP BY ASC/DESC 语法
15)将错误日志记录到系统日志配置的已删除系统变量
16)已删除的系统变量
17)具有新默认值的系统变量
18)零日期、日期时间和时间戳值
19)由于文件移除或损坏导致的数据库结构不一致
20)InnoDB 识别的属于其他引擎的表
21)由'check table x for upgrade'命令报告的问题
22)新的默认身份验证插件注意事项
23)不能有默认值的列
24)检查在 5.7 中使用的无效表名和库名
25)检查 5.7 中的孤立存储过程
26)检查在对象名称中使用单美元符号的过时用法
在进行 MySQL 升级时,务必关注这些潜在问题,并根据需要采取相应的措施。这将有助于确保升级过程顺利进行,同时避免在升级后出现问题。
检查项有可能返回三种 Upgrade_issue,分别是 ERROR、WARNING、NOTICE。
ERROR:必须整改,否则升级失败。 WARNING: 可能需要整改,否则升级后有功能异常或不符合预期。 NOTICE: 升级程序会帮您自动处理好的,仅仅通知您升级程序会干这操作。
mysqlshell 上 util.checkForServerUpgrade 的用法:
util.checkForServerUpgrade('fander@127.0.0.1:3307',{"password":"Dbops@8888","configPath":"/database/mysql/etc/3307/my.cnf"})
参数解释:
configPath - full path to MySQL server configuration file. outputFormat - value can be either TEXT (default) or JSON. targetVersion - version to which upgrade will be checked (default=8.0.32) password - password for connection.
其中,我没有填写 targetVersion,这个默认值等于 mysqlshell 的版本号,我打算升级到 MySQL8.0.32,所以下载安装用 mysqlshell 的版本也用了 8.0.32,这也是建议的用法。
因为你无法想象,mysqlshell8.0.11 能检查 MySQL8.0.32 版本的升级,对吧?所以 mysqlshell 必须下载大于等于要目标 MySQL 版本号的版本,建议就下载与目标版本一致的 mysqlshell 就可以了。

如果执行后,0 ERROR 就满足升级 MySQL8.0 的最低条件了,能跑通升级流程。其中 WARNINGS 也是需要注意的,部分 WARNINGS 是需要 DBA 们通知业务方提前修复的,以免升级后数据库使用起来不符合预期。
文章的组织
我会把 26 个检查项逐一介绍,由于文章篇幅的问题,文章会拆分成三篇,本文是第一篇,先介绍前 9 个检查项。
我会在 MySQL5.7.39 和 mysqlshell 8.0.32 下进行测试,结合 util.checkForServerUpgrade 的 C++ 源码、general log 展开分析。
我不太懂 C++ 语法,主要根据 util.checkForServerUpgrade 的源码结构分析我能看得懂的部分:
https://github.com/mysql/mysql-shell/blob/4be3fa62f11ca32350704f05f4b8a5b2383032fd/modules/util/upgrade_check.cc
这里是功能逻辑的主体,我能在这里确认工具检查数据库使用的方法,返回的报错类型,报错信息。
https://github.com/mysql/mysql-shell/blob/4be3fa62f11ca32350704f05f4b8a5b2383032fd/res/upgrade_checker/upgrade_checker.msg
这里存放了部分报错内容信息,还有报错信息对应的类似这种网址参考信息。
More information:
https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html#mysql-nutshell-removals
对于每个检查项,我会分 5 个步骤来分析:
A 检查的目的: 为什么要做这个检查。 B 检查的方法: 一般是直接下发 SQL 查出结果,有一些是下发 SQL 后程序加工处理后判断,有一些是检查配置文件,有一个是不检查直接弹出个 warnings。 C 异常类型和异常信息: 指出检查项返回的是 ERROR、WARNING、NOTICE 的哪一种,异常信息的文本内容是什么。 D 例子 E 点评
1. Usage of old temporal type
A 检查的目的
检查旧的时间类型使用。
在 MySQL5.6 时引入了一个新特性,支持 TIME、TIMESTAMP 和 DATETIME 微秒(小数秒)精度,最多 6 位。例如,在 MySQL5.5 下,TIMESTAMP 支持的精度是 '1970-01-01 00:00:01' ,而 MySQL5.6 下支持的精度到了 '1970-01-01 00:00:01.000000' 。
那么在 MySQL5.5 upgrade 到 MySQL5.6 版本,会自动转换时间类型吗?不会!在执行 upgrade 操作时甚至连 warnings 都没有!在错误日志里会有提示吗?也没有!
这就留下了坑,数据库里居然有两种时间类型,一种是旧的,一种是新的,并且我们使用时并不知道他的存在!
我们如何知道旧时间类型的存在?
有两种方法。
方法一
SELECT CASE isc.mtype
WHEN '6' THEN 'OLD'
WHEN '3' THEN 'NEW'
END FORMAT,
count(*) TOTAL
FROM information_schema.tables AS t
INNER JOIN information_schema.columns AS c ON c.table_schema = t.table_schema
AND c.table_name = t.table_name
LEFT OUTER JOIN information_schema.innodb_sys_tables AS ist ON ist.name = concat(t.table_schema,'/',t.table_name)
LEFT OUTER JOIN information_schema.innodb_sys_columns AS isc ON isc.table_id = ist.table_id
AND isc.name = c.column_name
WHERE c.column_type IN ('time','timestamp','datetime')
AND t.table_schema NOT IN ('mysql','information_schema','performance_schema')
AND t.table_type = 'base table'
AND (t.engine = 'innodb')
GROUP BY isc.mtype;
+--------+-------+
| FORMAT | TOTAL |
+--------+-------+
| NEW | 1 |
| OLD | 9 |
+--------+-------+
方法二
在 MySQL5.6 引入了一个参数 show_old_temporals
,默认是 OFF,设置 ON 可以使 show create table
时他们能凸显出来。
CREATE TABLE `mytbl` (
`ts` timestamp /* 5.5 binary format */ NOT NULL DEFAULT CURRENT_TIMESTAMP,
`dt` datetime /* 5.5 binary format */ DEFAULT NULL,
`t` time /* 5.5 binary format */ DEFAULT NULL
) DEFAULT CHARSET=latin1
MySQL 可以正确处理这两种类型,但是旧格式不能使用微秒,默认的 DATETIME 会占用更多磁盘空间。到目前为止,这种旧格式的时间类型没有对我们造成损害。
升级 5.7 可以消灭这种旧的时间格式吗?
答案是可以!当运行 5.7 的 mysql_upgrade 时,它会默认将旧字段转换为新格式,这意味着涉及旧时间类型的表都会用 alter table 语句重建。
....
test.t1
error : Table rebuild required. Please do "ALTER TABLE `t1` FORCE" or dump/reload to fix it!
test.t2
error : Table rebuild required. Please do "ALTER TABLE `t2` FORCE" or dump/reload to fix it!
test.t3
error : Table rebuild required. Please do "ALTER TABLE `t3` FORCE" or dump/reload to fix it!
Repairing tables
mysql.proxies_priv OK
`test`.`t1`
Running : ALTER TABLE `test`.`t1` FORCE
status : OK
`test`.`t2`
Running : ALTER TABLE `test`.`t2` FORCE
status : OK
`test`.`t3`
Running : ALTER TABLE `test`.`t3` FORCE
status : OK
Upgrade process completed successfully.
Checking if update is needed.
例子中,t1、t2、t3 表分别拥有 timestamp、datetime、date 的旧时间类型
mysql_upgrade 默认检查和升级所有表,但也允许加一个参数 --upgrade-system-tables
(短参数是 -s), 使 mysql_upgrade 只检查和升级系统表,而不检查业务表。
这种情况下,祖传的 MySQL5.5 版本的旧时间格式会继续保留在 MySQL5.7 里,缺点依然是旧格式不能使用微秒,默认的 DATETIME 会占用更多磁盘空间。老坑被保留下来。
MySQL8.0 不支持这种旧时间类型了,所以要提前检查出来,并要求用户自行重建这些表。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
SET show_old_temporals = ON;
SELECT table_schema, table_name,column_name,column_type FROM information_schema.columns WHERE column_type LIKE 'timestamp /* 5.5 binary format */';
SET show_old_temporals = OFF;
C 异常类型和异常信息
Upgrade_issue::ERROR
Following table columns use a deprecated and no longer supported timestamp disk storage format. They must be converted to the new format before upgrading. It can by done by rebuilding the table using 'ALTER TABLE <table_name> FORCE' command"
翻译:
以下表格列使用了已弃用且不再支持的旧版时间戳磁盘存储格式。在升级之前,必须将其转换为新格式。可以通过使用 'ALTER TABLE < 表名 > FORCE' 命令重建表来完成此操作。
D 例子
略
E 点评
绝大多数人留不下来这种坑。
2. MySQL8.0 syntax check for routine-like objects
A 检查的目的
类存储过程对象的 MySQL8.0 语法兼容检查。
检查 MySQL8.0 中四类对象(存储过程、函数、触发器和事件相关)的语法更改。这个检查的目的是确保升级到 MySQL8.0 后,这些对象的语法仍然有效。
B 检查的方法
下发到数据库以下 SQL,查出有哪些存储过程、函数、触发器和事件对象,然后对这些对象做简单语法检查。
SELECT ROUTINE_SCHEMA, ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_SCHEMA <> 'sys';
SELECT ROUTINE_SCHEMA, ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'FUNCTION' AND ROUTINE_SCHEMA <> 'sys';
SELECT TRIGGER_SCHEMA, TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA <> 'sys';
SELECT EVENT_SCHEMA, EVENT_NAME FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA <> 'sys';
C 异常类型和异常信息
Upgrade_issue::ERROR
The following objects did not pass a syntax check with the latest MySQL8.0 grammar. A common reason is that they reference names that conflict with new reserved keywords. You must update these routine definitions and quote
any such references before upgrading.
翻译:
以下对象未通过最新 MySQL8.0 语法的语法检查。一个常见的原因是它们引用了与新保留关键字冲突的名称。您必须在升级之前更新这些例程定义,并对这些引用加上引号。
D 例子
use fander;
create table check2_table(
a int,
b varchar(10)
);
DELIMITER $$
create procedure check2_procedure ()
begin
select count(1) from check2_table;
end $$
DELIMITER ;
update mysql.proc set body='sdafdsfdsfa' where db='fander' and name='check2_procedure';

E 点评
我没有找到一个很好的模拟报错例子,D 中的例子是直接把存储过程修改为一个完全不是 SQL 的句子,凑合看了,相信大多数人能通过本项检查。
3. Usage of db objects with names conflicting with new reserved keywords
A 检查的目的
与新保留字、新关键字冲突的数据库对象名称使用。
检查与新保留关键字冲突的数据库对象(数据库、表、列、触发器、视图、存储过程和函数、事件)名称。每个对象的检查使用一条 SQL 检查,一共 7 个对象对应 7 条 SQL。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
select SCHEMA_NAME, 'Schema name' as WARNING from INFORMATION_SCHEMA.SCHEMATA where SCHEMA_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
SELECT TABLE_SCHEMA, TABLE_NAME, 'Table name' as WARNING FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE != 'VIEW' and TABLE_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, 'Column name' as WARNING FROM information_schema.columns WHERE TABLE_SCHEMA not in ('information_schema', 'performance_schema') and COLUMN_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
SELECT TRIGGER_SCHEMA, TRIGGER_NAME, 'Trigger name' as WARNING FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
SELECT TABLE_SCHEMA, TABLE_NAME, 'View name' as WARNING FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, 'Routine name' as WARNING FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
SELECT EVENT_SCHEMA, EVENT_NAME, 'Event name' as WARNING FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_NAME in ('ADMIN', 'CUBE', 'CUME_DIST', 'DENSE_RANK', 'EMPTY', 'EXCEPT', 'FIRST_VALUE', 'FUNCTION', 'GROUPING', 'GROUPS', 'JSON_TABLE', 'LAG', 'LAST_VALUE', 'LEAD', 'NTH_VALUE', 'NTILE', 'OF', 'OVER', 'PERCENT_RANK', 'PERSIST', 'PERSIST_ONLY', 'RANK', 'RECURSIVE', 'ROW', 'ROWS', 'ROW_NUMBER', 'SYSTEM', 'WINDOW', 'LATERAL', 'ARRAY' ,'MEMBER', 'FULL');
C 异常类型和异常信息
Upgrade_issue::WARNINGThe following objects have names that conflict with new reserved keywords. Ensure queries sent by your applications use quotes
when referring to them or they will result in errors.
翻译:
以下对象的名称与新保留关键字冲突。确保您的应用程序发送的查询在引用这些对象时使用反引号 `
括起来,否则将导致错误。
D 例子
use fander;
create table check3_table(
a int,
b varchar(10)
);
DELIMITER $$
create procedure WINDOW ()
begin
select count(1) from check3_table;
end $$
DELIMITER ;

E 点评
数据库、表、列、触发器、视图、存储过程和函数、事件等七种对象和 8.0 的新关键字或保留字有冲突,这是有可能的。如果发现的话可以考虑重建换个名字,或者用反引号 `
括起来。
4. Usage of utf8mb3 charset
A 检查的目的
使用 utf8mb3 字符集的情况。
使用两个 SQL,分别检查哪些库、哪些表或列使用了 utf8 或者 utf8mb3 字符集。
检查的原因是,在 MySQL5.7 下如果不设置参数 default-character-set
,默认值是 latin1,大多数人会在装库后修改为 utf8,少数人会修改为 utf8mb4,而 MySQL8.0 的 default-character-set
默认值是 utf8mb4 了。
在 MySQL5.7 中 utf8 等于 utf8mb3,是别名关系,建表是无论指定 utf8 或者 utf8mb3,show create 语句都会显示为 utf8,但 MySQL8.0 中,建表是无论指定 utf8 或者 utf8mb3,show create 语句都会显示为 utf8mb3,而不是 utf8 了。
# 升级前,5.7
mysql> show create table table_utf8\G
*************************** 1. row ***************************
Table: table_utf8
Create Table: CREATE TABLE `table_utf8` (
`a` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
# 升级后,8.0
mysql> show create table table_utf8;
+------------+----------------------------------------------------------------------------------------------------+
| Table | Create Table |
+------------+----------------------------------------------------------------------------------------------------+
| table_utf8 | CREATE TABLE `table_utf8` (
`a` varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
+------------+----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
在 MySQL8.0,如果建表/建库指定 utf8,有以下警告。
mysql> create database fander123213123213 DEFAULT CHARACTER SET utf8;
Query OK, 1 row affected, 1 warning (0.04 sec)
mysql> show warnings;
+---------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 3719 | 'utf8' is currently an alias for the character set UTF8MB3, but will be an alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to be unambiguous. |
+---------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
意思是在 9.0 版本(暂时这么叫吧),utf8 将改为等价于 utf8mb4,而不是 utf8mb3 了,这等于有兼容性修改了。所以 MySQL8.0 官方强烈要求现在就改为 utf8mb4 了!
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
#库
select SCHEMA_NAME, concat('schema''s default character set: ', DEFAULT_CHARACTER_SET_NAME) from INFORMATION_SCHEMA.schemata where SCHEMA_NAME not in ('information_schema', 'performance_schema', 'sys') and DEFAULT_CHARACTER_SET_NAME in ('utf8', 'utf8mb3');
#表或列
select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, concat('column''s default character set: ',CHARACTER_SET_NAME) from information_schema.columns where CHARACTER_SET_NAME in ('utf8', 'utf8mb3') and TABLE_SCHEMA not in ('sys', 'performance_schema', 'information_schema', 'mysql');
C 异常类型和异常信息
Upgrade_issue::WARNING
The following objects use the utf8mb3 character set. It is recommended to convert them to use utf8mb4 instead, for improved Unicode support.
翻译:
以下对象使用 utf8mb3 字符集。建议将它们转换为使用 utf8mb4,以获得更好的 Unicode 支持。
例子
create database fander1 DEFAULT CHARACTER SET utf8;
create database fander2 DEFAULT CHARACTER SET utf8mb3;
use fander2;
create table table_fander2 (a varchar(10))charset=utf8mb3;



E 点评
个人认为这个仅仅是提醒 utf8mb3 和 utf8 的使用存在风险。一是 utf8mb3 不像 utf8mb4 这种能支持更多字符如 emoji 表情,二是 MySQL9.0 的 utf8 不再等价 utf8mb3 了,而是等价于 utf8mb4 了,未来继续按之前理解来使用可能有问题。 这两个痛点无关痛痒,这步骤可以不修复,对升级没有影响。
5. Table names in the mysql schema conflicting with new tables in 8.0
A 检查的目的
mysql 库中的表名与 8.0 中的新表冲突。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
SELECT TABLE_SCHEMA, TABLE_NAME, 'Table name used in mysql schema in 8.0' as WARNING FROM INFORMATION_SCHEMA.TABLES WHERE LOWER(TABLE_SCHEMA) = 'mysql' and LOWER(TABLE_NAME) IN ('catalogs', 'character_sets', 'collations', 'column_type_elements', 'columns', 'dd_properties', 'events', 'foreign_key_column_usage', 'foreign_keys', 'index_column_usage', 'index_partitions', 'index_stats', 'indexes', 'parameter_type_elements', 'parameters', 'routines', 'schemata', 'st_spatial_reference_systems', 'table_partition_values', 'table_partitions', 'table_stats', 'tables', 'tablespace_files', 'tablespaces', 'triggers', 'view_routine_usage', 'view_table_usage', 'component', 'default_roles', 'global_grants', 'innodb_ddl_log', 'innodb_dynamic_metadata', 'password_history', 'role_edges');
C 异常类型和异常信息
Upgrade_issue::ERROR
The following tables in mysql schema have names that will conflict with the ones introduced in 8.0 version. They must be renamed or removed before upgrading (use RENAME TABLE command). This may also entail changes to applications that use the affected tables.
翻译:
以下 mysql 库中的表名与 8.0 版本中引入的表名冲突。在升级之前必须对它们进行重命名或删除(使用 RENAME TABLE 命令)。这可能还需要对使用受影响表的应用程序进行更改。
D 例子
create table mysql.columns (a int);

E 点评
系统库 mysql 库里是允许持久化地建个人表的,但我们从来不会在系统库 mysql 库里这么干,相信这个检查项对大多数人都没有影响。如果您有这样做,请确认为什么必须建在 mysql 库里,否则都应该迁移到自有的库里,实在无法迁移,就是如官方建议那样, rename table 修改名字,并进行应用程序的更改。
6. Partitioned tables using engines with non native partitioning
A 检查的目的
检查使用了非本地分区引擎的分区表的情况。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
select table_schema, table_name, concat(engine, ' engine does not support native partitioning') from information_schema.Tables where create_options like '%partitioned%' and upper(engine) not in ('INNODB', 'NDB', 'NDBCLUSTER');
C 异常类型和异常信息
Upgrade_issue::ERROR,
In MySQL8.0 storage engine is responsible for providing its own partitioning handler, and the MySQL server no longer provides generic partitioning support. InnoDB and NDB are the only storage engines that provide a native partitioning handler that is supported in MySQL8.0. A partitioned table using any other storage engine must be altered—either to convert it to InnoDB or NDB, or to remove its partitioning—before upgrading the server, else it cannot be used afterwards.
翻译:
在 MySQL8.0 中,存储引擎负责提供自己的分区处理器,MySQL 服务器不再提供通用分区支持。InnoDB 和 NDB 是唯一在 MySQL8.0 中提供本地分区处理器支持的存储引擎。使用其他存储引擎的分区表在升级服务器之前必须进行更改——将其转换为 InnoDB 或 NDB,或删除其分区,否则升级后无法使用。
D 例子
use fander
create table check6_table
(
id int unsigned not null auto_increment,
name varchar(64) not null,
parent_id int not null,
primary key (id,parent_id),
key(parent_id)
)
partition by list(parent_id)
(
partition p1 values in (1,2,3,6,9)engine = myisam,
partition p2 values in (4,5,10,22,23)engine = myisam,
partition p3 values in (7,8,11,12,13)engine = myisam,
partition p4 values in (14,15,16,17,20)engine = myisam,
partition p5 values in (18,19,21,24,25)engine = myisam
);

E 点评
MySQL 常用就两大存储引擎,InnoDB 和 MyISAM。MySQL8.0 开始不支持使用非本地分区引擎的分区表,除了 InnoDB 和 NDB,其他都是非本地分区引擎,所以这个检查项主要是抓出哪些用 MyISAM 分区表的情况。MyISAM 实际上我们很早就弃用了,用 MyISAM 分区表更不可能了。这个检查项大概率没问题。
MyISAM 有什么危害,请阅读我这篇文章《交维规范系列 | 为什么我们禁止使用 MyISAM 存储引擎》
7. Foreign key constraint names longer than 64 characters
A 检查的目的
检查外键约束名超过 64 个字符的情况。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
select table_schema, table_name, 'Foreign key longer than 64 characters' as description from information_schema.tables where table_name in (select left(substr(id,instr(id,'/')+1), instr(substr(id,instr(id,'/')+1),'_ibfk_')-1) from information_schema.innodb_sys_foreign where length(substr(id,instr(id,'/')+1))>64);
C 异常类型和异常信息
Upgrade_issue::ERROR
The following tables must be altered to have constraint names shorter than 64 characters (use ALTER TABLE).
翻译:
以下表格必须修改以使约束名长度短于 64 个字符(使用 ALTER TABLE)。
D 例子
mysql> CREATE TABLE courses (
-> id INT PRIMARY KEY,
-> name VARCHAR(255) NOT NULL
-> );
courses(id)
);
Query OK, 0 rows affected (0.05 sec)
mysql>
mysql> CREATE TABLE students (
-> id INT PRIMARY KEY,
-> course_id INT,
-> CONSTRAINT fk_diqiuzhonghuarenmingongheguoguangdongshengshenzhenshilonggangqu FOREIGN KEY (course_id) REFERENCES courses(id)
-> );
ERROR 1059 (42000): Identifier name 'fk_diqiuzhonghuarenmingongheguoguangdongshengshenzhenshilonggangqu' is too long
E 点评
事实上我造不出外键约束名称超过 64 个字符的例子,MySQL 从 5.0 版本起就限制了外键名称最大长度为 64 个字符。不知道检查的必要性又是哪个 bug 造成的呢?
8. Usage of obsolete MAXDB sql_mode flag
A 检查的目的
检查使用了过时的 MAXDB sql_mode 标志的情况。
检查的对象包括存储过程与函数、事件、触发器、参数。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
select routine_schema, routine_name, concat(routine_type, ' uses obsolete MAXDB sql_mode') from information_schema.routines where find_in_set('MAXDB', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete MAXDB sql_mode' from information_schema.EVENTS where find_in_set('MAXDB', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete MAXDB sql_mode' from information_schema.TRIGGERS where find_in_set('MAXDB', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete MAXDB option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('MAXDB', variable_value);
C 异常类型和异常信息
Upgrade_issue::WARNING
The following DB objects have the obsolete MAXDB option persisted for sql_mode, which will be cleared during upgrade to 8.0. It can potentially change the datatype DATETIME into TIMESTAMP if it is used inside object's definition, and this in turn can change the behavior in case of dates earlier than 1970 or later than 2037. If this is a concern, please redefine these objects so that they do not rely on the MAXDB flag before running the upgrade to 8.0.
翻译:
以下数据库对象在 sql_mode 中保留了已过时的 MAXDB 选项,在升级到 8.0 时将被清除。如果在对象定义中使用,这可能会将数据类型 DATETIME 更改为 TIMESTAMP,反过来,在 1970 年之前或 2037 年之后的日期中,可能会改变行为。如果这是一个担忧,请在运行升级到 8.0 之前重新定义这些对象,以便它们不依赖于 MAXDB 标志。
D 例子
SET GLOBAL sql_mode = sys.list_add(@@session.sql_mode, 'MAXDB');

E 点评
实际上"8.Usage of obsolete MAXDB sql_mode flagMAXDB"和"9.Usage of obsolete sql_mode flags"检查项几乎一样的。前者是单独检查了 8.0 要移除的 sql_mode:MAXDB,后者检查了其余要移除的 10 个 sql_mode。为什么单独检查 MAXDB 这个 sql_mode,因为他这个 sql_mode 比较特殊,为了和 MAXDB 兼容,这个 sql_mode 下会自动在 CREATE/ALTER TABLE 时将 TIMESTAMP 列类型偷偷地替换为 DATETIME 类型。
mysql> use fander
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> create table check9_table (a int, b TIMESTAMP);
Query OK, 0 rows affected (0.03 sec)
mysql> show create table check9_table\G
*************************** 1. row ***************************
Table: check9_table
Create Table: CREATE TABLE "check9_table" (
"a" int(11) DEFAULT NULL,
"b" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
)
1 row in set (0.00 sec)
mysql> SET session sql_mode = sys.list_add(@@session.sql_mode, 'MAXDB');
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> create table check9_table_with_MAXDB (a int, b TIMESTAMP);
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> show create table check9_table_with_MAXDB\G
*************************** 1. row ***************************
Table: check9_table_with_MAXDB
Create Table: CREATE TABLE "check9_table_with_maxdb" (
"a" int(11) DEFAULT NULL,
"b" datetime DEFAULT NULL
)
1 row in set (0.00 sec)
所以,这个 sql_mode 虽然可以在 MySQL8.0 升级时被处理,但 mysqlshell 有义务弹出个 warnings 提醒你,有逻辑的改变!而后面的 "9. Usage of obsolete sql_mode flags" 弹出的是 NOTICE 级别的提示,只是提示你那些 sql_mode 在升级时被自动删除了,但没有 MAXDB 这种逻辑修改的问题,不需要 warnings。
9. Usage of obsolete sql_mode flags
A 检查的目的
检查使用了过时的 sql_mode 标志的情况,包括以下 10 种。
{"DB2", "MSSQL", "MYSQL323", "MYSQL40", "NO_AUTO_CREATE_USER",
"NO_FIELD_OPTIONS", "NO_KEY_OPTIONS", "NO_TABLE_OPTIONS", "ORACLE",
"POSTGRESQL"}
检查的对象包括存储过程与函数、事件、触发器、参数。
B 检查的方法
下发到数据库以下 SQL,如果检查发现有结果,则返回 Upgrade_issue 对象。
select routine_schema, routine_name, concat(routine_type, ' uses obsolete DB2 sql_mode') from information_schema.routines where find_in_set('DB2', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete DB2 sql_mode' from information_schema.EVENTS where find_in_set('DB2', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete DB2 sql_mode' from information_schema.TRIGGERS where find_in_set('DB2', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete DB2 option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('DB2', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete MSSQL sql_mode') from information_schema.routines where find_in_set('MSSQL', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete MSSQL sql_mode' from information_schema.EVENTS where find_in_set('MSSQL', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete MSSQL sql_mode' from information_schema.TRIGGERS where find_in_set('MSSQL', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete MSSQL option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('MSSQL', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete MYSQL323 sql_mode') from information_schema.routines where find_in_set('MYSQL323', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete MYSQL323 sql_mode' from information_schema.EVENTS where find_in_set('MYSQL323', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete MYSQL323 sql_mode' from information_schema.TRIGGERS where find_in_set('MYSQL323', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete MYSQL323 option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('MYSQL323', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete MYSQL40 sql_mode') from information_schema.routines where find_in_set('MYSQL40', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete MYSQL40 sql_mode' from information_schema.EVENTS where find_in_set('MYSQL40', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete MYSQL40 sql_mode' from information_schema.TRIGGERS where find_in_set('MYSQL40', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete MYSQL40 option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('MYSQL40', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete NO_AUTO_CREATE_USER sql_mode') from information_schema.routines where find_in_set('NO_AUTO_CREATE_USER', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete NO_AUTO_CREATE_USER sql_mode' from information_schema.EVENTS where find_in_set('NO_AUTO_CREATE_USER', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete NO_AUTO_CREATE_USER sql_mode' from information_schema.TRIGGERS where find_in_set('NO_AUTO_CREATE_USER', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete NO_AUTO_CREATE_USER option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('NO_AUTO_CREATE_USER', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete NO_FIELD_OPTIONS sql_mode') from information_schema.routines where find_in_set('NO_FIELD_OPTIONS', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete NO_FIELD_OPTIONS sql_mode' from information_schema.EVENTS where find_in_set('NO_FIELD_OPTIONS', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete NO_FIELD_OPTIONS sql_mode' from information_schema.TRIGGERS where find_in_set('NO_FIELD_OPTIONS', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete NO_FIELD_OPTIONS option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('NO_FIELD_OPTIONS', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete NO_KEY_OPTIONS sql_mode') from information_schema.routines where find_in_set('NO_KEY_OPTIONS', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete NO_KEY_OPTIONS sql_mode' from information_schema.EVENTS where find_in_set('NO_KEY_OPTIONS', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete NO_KEY_OPTIONS sql_mode' from information_schema.TRIGGERS where find_in_set('NO_KEY_OPTIONS', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete NO_KEY_OPTIONS option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('NO_KEY_OPTIONS', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete NO_TABLE_OPTIONS sql_mode') from information_schema.routines where find_in_set('NO_TABLE_OPTIONS', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete NO_TABLE_OPTIONS sql_mode' from information_schema.EVENTS where find_in_set('NO_TABLE_OPTIONS', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete NO_TABLE_OPTIONS sql_mode' from information_schema.TRIGGERS where find_in_set('NO_TABLE_OPTIONS', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete NO_TABLE_OPTIONS option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('NO_TABLE_OPTIONS', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete ORACLE sql_mode') from information_schema.routines where find_in_set('ORACLE', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete ORACLE sql_mode' from information_schema.EVENTS where find_in_set('ORACLE', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete ORACLE sql_mode' from information_schema.TRIGGERS where find_in_set('ORACLE', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete ORACLE option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('ORACLE', variable_value);
select routine_schema, routine_name, concat(routine_type, ' uses obsolete POSTGRESQL sql_mode') from information_schema.routines where find_in_set('POSTGRESQL', sql_mode);
select event_schema, event_name, 'EVENT uses obsolete POSTGRESQL sql_mode' from information_schema.EVENTS where find_in_set('POSTGRESQL', sql_mode);
select trigger_schema, trigger_name, 'TRIGGER uses obsolete POSTGRESQL sql_mode' from information_schema.TRIGGERS where find_in_set('POSTGRESQL', sql_mode);
select concat('global system variable ', variable_name), 'defined using obsolete POSTGRESQL option' as reason from performance_schema.global_variables where variable_name = 'sql_mode' and find_in_set('POSTGRESQL', variable_value);
C 异常类型和异常信息
Upgrade_issue::NOTICE
The following DB objects have obsolete options persisted for sql_mode, which will be cleared during upgrade to 8.0.
翻译:
以下数据库对象中保存了针对 sql_mode 的过时选项,这些选项将在升级到 8.0 时被清除。
D 例子
mysql> show variables like '%sql_mode%'\G
*************************** 1. row ***************************
Variable_name: sql_mode
Value: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
1 row in set (0.01 sec)

就采用 MySQL5.7 默认的 sql_mode 就会有 NOTICE 提醒,因为 MySQL8.0 把 NO_AUTO_CREATE_USER 这个默认的 sql_mode 移除了。
E 点评
NOTICE 级别意味着仅仅通知我们,自动移除了 NO_AUTO_CREATE_USER,移除得好!
MySQL5.7 默认就添加了 NO_AUTO_CREATE_USER 这个 sql_mode,这非常好,因为 MySQL 默认可以通过 grant 语法直接创建数据库用户(grant 不是授权语法吗?听起来这行为很傻逼啊)。而加了 NO_AUTO_CREATE_USER 可以防止通过 grant 自动创建数据库用户。
# 我在 MySQL5.7 默认的 sql_mode 基础上移除了 NO_AUTO_CREATE_USER 的限制
# 现在我可以直接通过 grant 创建用户了!
mysql> SET session sql_mode = sys.list_drop(@@session.sql_mode, 'NO_AUTO_CREATE_USER');
Query OK, 0 rows affected (0.00 sec)
# 我想新建一个数据库管理员,名叫"芬达橙"
# 我还很机智地设置了可信网段,使我的数据库账号只有 192.168.199.% 网段才可以访问
mysql> create user fanderchan@'192.168.199.%' identified by 'Dbops@8888';
Query OK, 0 rows affected (0.09 sec)
# 但我授权的时候,我忘记填网段了。
mysql> grant all on *.* to fanderchan;
Query OK, 0 rows affected, 1 warning (0.01 sec)
# 恐怖的事情发生了,我的数据库悄悄地多了一个名叫 fanderchan,可信网段无限制的用户。
# 最恐怖的事是他拥有超级管理员身份,密码为空。。
mysql> select user,host,authentication_string from mysql.user where user='fanderchan';
+------------+---------------+-------------------------------------------+
| user | host | authentication_string |
+------------+---------------+-------------------------------------------+
| fanderchan | 192.168.199.% | *E6745150841627933C00DF70C7FA193B063E9303 |
| fanderchan | % | |
+------------+---------------+-------------------------------------------+
2 rows in set (0.00 sec)
我模拟了一个例子,在去掉了 sql_mode: NO_AUTO_CREATE_USER 的情况下,因为操作失误,grant 语法以空密码的方式创建了一个多余的账号。我并未察觉到这个账号的存在,而它却拥有空密码、最高权限,且无网段限制的访问。
正因为这个安全隐患,MySQL8.0 直接移除了 NO_AUTO_CREATE_USER 这个 sql_mode,不允许设置,grant 默认就不支持创建用户。在 MySQL8.0 中,默认必须先执行 create user,再执行 grant。





