暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

MySQL 8.0原地升级操作指南

腾讯游戏存储与计算技术 2021-05-19
4475

现今已是MySQL 8.0推出的第四个年头,MySQL 8.0的特性介绍在网上屡见不鲜,但却鲜有对与实际应用直接相关的旧版本升级问题的解答,或是相关的操作手册。这也是本文的核心,本文将以MySQL官方手册为主,细节补充为辅,对MySQL 8.0的升级(主要是原地升级)提供操作上的指南,若能为广大的MySQL使用者提供一定的帮助,则甚是幸运。

1. 为什么选择MySQL 8.0

自2018年4月MySQL的首个GA版本MySQL 8.0.11正式发布后,现如今已是MySQL 8.0推出的第四个年头,在这周的5月11日,MySQL也发布了新版本8.0.25,有关“我该不该升级到MySQL 8.0”的问题,经过三年来国内外行业内广泛的测试,也在互联网上出现了许多值得参考的信息。

在特性上,作为一个大版本升级,MySQL 8.0带来的是包含功能(NoSQL的支持、窗口函数的引入、JSON支持的完善)、可靠性(事务型的数据字典、原子化的DDL语句)、可观测性(Performance Schema升级、隐藏索引特性)、可管理性(参数修改持久化)、安全性(角色管理)在内的全方位升级,此外还有不计其数的变更与改进。关于性能,在我们的实测中MySQL 8.0表现也不乏可圈可点之处,在innodb_flush_log_at_trx_commit=1
的write-only场景,我们成功复现了MySQL官方宣称的两倍性能提升

所以,关于“该不该升级到MySQL 8.0”的这个讨论,现如今已经不是“MySQL 8.0够不够好”的问题,而更多的是“我有没有准备好升级”的问题。

2. 升级方式选择

进行MySQL版本的升级,主要有两种方式。

一种是逻辑升级(Logical Upgrade),通过mysqldump
等工具对旧实例进行实例级的逻辑备份,导出SQL文件,再于一个新数据目录下拉起一个新的MySQL 8.0实例,通过执行SQL文件的方式导入旧实例数据。执行逻辑升级的好处是安全、可控,执行SQL的数据导入方式有更高的升级成功率,即便升级失败也不会对旧实例的原数据造成影响。但当待升级的旧实例数据量大的时候,也难以避免逻辑备份、数据导入耗时长的问题。

还有一种是原地升级(In-place Upgrade),即在旧实例的原数据目录下拉起新实例,直接对原数据进行升级。相比逻辑升级的方式,原地升级无疑在耗时上更加友好,因为其除了必要的系统库表升级、数据兼容性检查外没有更多的额外操作,省去了数据备份与数据导入的麻烦。但原地升级同样也有明显的缺点,那就是如果升级过程中由于原数据目录下的已有数据与新版本的不兼容等问题,导致了升级失败,那么原数据目录有可能被造成无法回滚的污染,即便MySQL在原地升级流程上做了不少可靠性的保障。

总而言之,选择哪一种升级方式应根据实例数据的规模与复杂度、DBA或应用侧对升级耗时的容忍度等实际情况而定,但无论选择哪一种方式,提前对原数据进行备份都是非常必要的操作。

须注意,MySQL的原地升级历来仅支持跨一个大版本的升级,即MySQL 5.7可以原地升级至MySQL 8.0,但MySQL 5.6至MySQL 8.0的原地升级不被支持。

由于MySQL 5.7到MySQL 8.0的版本变化较大,使得原地升级的复杂度大大提升,而用户在升级前也不得不因此做更多的升级检查,以下内容将着重介绍原地升级前的必要检查工作,以及正式升级的相关操作。

3. 原地升级检查 (Precheck)

以下内容主要参考MySQL官方手册[1],若对下列步骤中细节存在疑问可点击查阅。

1.调用mysqlcheck

执行mysqlcheck -u root -p --all-databases --check-upgrade
,其对以下事项进行检查:

是否存在使用废弃的数据类型或函数的表,如表中含有5.6.4前格式的TIME
DATETIME
TIMESTAMP
数据类型(没有对小数秒的精度支持,如1.xx秒),则无法进行8.0的原地升级,这种情况一般在当前MySQL 5.7实例是由MySQL 5.6或更旧版本迭代原地升级的场景下出现,可通过REPAIR TABLE
进行修复
是否存在孤儿.frm
文件(没有.ibd
文件与之对应)。
已有触发器的定义是否存在问题。

若执行命令返回结果均为OK
,则此步没有问题,可以进行下一步检查。若此步出现问题,则需根据错误原因手动进行修复,也可以在调用mysqlcheck
的时候增加--auto-repair
的选项,自动尝试执行REPAIR TABLE
进行修复,须注意自动修复并不保证问题能被正确解决,且有一定可能造成数据目录被污染。

2.检查表分片问题

MySQL 8.0中,只对InnoDB
NDB Cluster
两种存储引擎提供原生的分片支持,因此旧版本中如果存在基于非以上两种存储引擎的表(如MyISAM
表),并且使用了分片(Partition),则原地升级无法进行。

要检查是否存在这样的表,可执行以下SQL查询:

SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE ENGINE NOT IN ('innodb', 'ndbcluster')
AND CREATE_OPTIONS LIKE '%partitioned%';

若结果为空,则可以进行下一步检查。若结果不为空,可通过两种方式解决:

将原表修改成InnoDB
表,执行

ALTER TABLE table_name ENGINE = INNODB;

     •取消原表的分片,执行

ALTER TABLE table_name REMOVE PARTITIONING;

3.新增保留字

MySQL 8.0新增了一些保留字,包括:

CUME_DIST, DENSE_RANK, EMPTY, EXCEPT, FIRST_VALUE, GROUPING, GROUPS, JSON_TABLE, LAG, LAST_VALUE, LATERAL, LEAD, NTH_VALUE, NTILE, OF, OVER, PERCENT_RANK, RANK, RECURSIVE, ROW_NUMBER, SYSTEM, WINDOW

建议查询information_schema
中的TABLES
COLUMNS
VIEWS
,ROUTINES
TRIGGERS
等表,检查是否存在使用这些保留字作为标识符的情况,若存在,则应检查应用侧的语句是否相应地正确使用了反引号标识符。

4.检查数据字典表名冲突

原地升级要求mysql
库中不能存在与MySQL 8.0的数据字典表同名的表,通过执行以下SQL查询可以检测该问题:

SELECT TABLE_SCHEMA, TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE LOWER(TABLE_SCHEMA) = 'mysql'
and LOWER(TABLE_NAME) IN
(
'catalogs',
'character_sets',
'check_constraints',
'collations',
'column_statistics',
'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',
'resource_groups',
'routines',
'schemata',
'st_spatial_reference_systems',
'table_partition_values',
'table_partitions',
'table_stats',
'tables',
'tablespace_files',
'tablespaces',
'triggers',
'view_routine_usage',
'view_table_usage'
);

若查询结果为空,则可以进行下一步检查。若不为空,则需通过DROP TABLE
删除同名表或通过RENAME TABLE
进行重命名。

5.检查是否存在长度大于64的外键名

原地升级要求任何表均不存在外键名长度大于64的外键。

进行此项的检查,可执行以下语句:

SELECT TABLE_SCHEMA, TABLE_NAME
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);

若查询结果为空,则可以进行下一步检查。若不为空,需drop相应外键后再重新添加该外键,并赋长度不超过64的新命名

6.检查sql_mode
变量是否含废弃值

相比MySQL 5.7,MySQL 8.0的sql_mode
不再支持NO_AUTO_CREATE_USER
,这是由于MySQL 8.0中不再支持使用GRANT
语句来创建用户,若配置中使用了该值,则需手动移除。

7.检查是否存在列名长度大于64的视图

MySQL 5.7允许创建视图中的列名长度最大达255字符,在8.0中这一限制被调整为64字符,因此存在列名长度大于64的视图需手动进行调整。检测这样的视图的方式是通过SHOW CREATE VIEW
手动检查视图定义,也可通过查询INFORMATION_SCHEMA.VIEWS
得到已创建的视图及其定义。

8.检查ENUM
SET
元素字符长度和

MySQL 8.0要求表或存储过程中的ENUM
列或SET
列的所有元素字符长度和不超过255字符,总大小不超过1020字节(考虑到多字节字符集的情况),在MySQL 8.0前的版本,这一限制是64K字节。若存在这样的情况,需通过手动修改定义进行修复。

9.检查是否存在InnoDB
共享表空间下的表分片

当升级到8.0.13及以上的版本时,需确保没有存在于InnoDB
共享表空间下的表分片,通过以下SQL查询检测是否有这样的分片:

SELECT DISTINCT NAME, SPACE, SPACE_TYPE FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES
WHERE NAME LIKE '%#P#%' AND SPACE_TYPE NOT LIKE 'Single';

若查询结果不为空,需将分片从共享表空间移到innodb_file_per_table
表空间,可通过执行以下语句:

ALTER TABLE table_name REORGANIZE PARTITION partition_name
INTO (partition_definition TABLESPACE=innodb_file_per_table);

10.检查GROUP BY
语句是否使用ASC
DESC

原地升级前需检查查询语句或存储过程定义中是否存在GROUP BY
语句使用ASC
DESC
的情况,若存在则需进行移除,因为MySQL 8.0.13及以上的版本不再支持相应语法。

11.检查是否使用了MySQL 8.0不支持的特性

一些MySQL 5.7的mysqld
启动选项或系统变量不再被MySQL 8.0支持,应在启动配置及应用中移除对废弃特性的引用。

参考:

1.4 Server and Status Variables and Options Added, Deprecated, or Removed in MySQL 8.0[2]

Features Removed in MySQL 8.0[3]

12.若需设置lower_case_table_names=1
,检查库表名是否均为小写

系统变量lower_case_table_names
控制表名的存储与比较是否大小写敏感,其支持三个值:

lower_case_table_names=0
时,表名中的字母按实际提供的大小写存储,Server进行表名比较时大小写敏感。
lower_case_table_names=1
时,表名中的字母全部转为小写存储,Server进行表名比较时忽略大小写。
lower_case_table_names=2
时,表名中的字母按实际提供的大小写存储,Server进行表名比较时忽略大小写。

若在旧实例上原始lower_case_table_names
值不为1,且需要在本次升级同时设置lower_case_table_names=1
,需先确保原数据中的所有库表名均为小写,可通过执行以下查询检查:

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME != LOWER(TABLE_NAME) AND TABLE_TYPE = 'BASE TABLE';
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME != LOWER(SCHEMA_NAME);

若存在库表名中有大写字母的情况,则不应在本次升级时设置lower_case_table_names=1
,因为其会导致原地升级失败。


以上检查之所以必要,是因为MySQL 8.0在语法上、Server执行指令的逻辑上,以及特性支持上均有不少的改动。以上检查固然繁琐,但所幸,若用户非常不耐烦地跳过了检查,选择了直接原地升级,MySQL亦提供了后备保障。根据MySQL官方文档:

若在原地升级过程中由于以上任一原因导致升级失败,mysql server会回滚对数据字典的所有变更,此时只需手动删除生成的redo log文件,可以在原数据目录下重新拉起MySQL 5.7实例。针对错误原因进行修复后,执行一次slow shutdown(innodb_fast_shutdown=0
)后即可重新尝试原地升级。

4. 正式升级

在以往的原地升级(如MySQL 5.6 ==> MySQL 5.7)流程中,正式升级需要分为以下步骤:

1.首先需要关闭待升级的旧实例:

mysqladmin -uroot -p shutdown

    2.获取目标版本的二进制,方式有很多,可以通过yum
等包管理工具下载,可以从官方获取二进制tar包,亦可以获取目标版本的源码自行编译。
    3.使用目标版本的mysqld_safe
mysqld
,拉起新实例:

mysqld_safe --datadir=/path/to/existing-datadir --user=mysql &

    4.执行mysql_upgrade
mysql
 、information_schema
 、performance_schema
 、sys
等系统库进行升级,并检查用户库表与当前版本MySQL的兼容性:

mysql_upgrade -u root -p

    5.重启实例,检查升级是否生效:

mysqladmin -u root -p shutdown
mysqld_safe --user=mysql --datadir=/path/to/existing-datadir &

若本次升级的目标版本小于MySQL 8.0.16,则正式升级的步骤与上述一致。

而从MySQL 8.0.16开始,mysql_upgrade
被正式淘汰,其对系统库的升级以及数据兼容性检查等操作已全部被MySQL Server包揽。也就是说,在进行第三步拉起新实例的过程中,mysqld
即自动检查是否需要更新,并在需要更新的情况下自动执行所有更新相关的操作。因此,当目标版本大于或等于MySQL 8.0.16时,若第三步中新实例已成功拉起,没有报错,则本次升级成功,后续无需执行任何升级相关操作,可以正常使用。这一改变也确实给升级的流程带来不小的便利。

5. 其他

原地升级过程中,除了第三节所介绍的需检查事项可能造成升级失败外,在我们团队的实际升级测试中,还发现了一个隐藏很深的字符编码坑,是由MySQL 5.7与MySQL 8.0在处理建表语句过程的字符编码合法性检查上的差异导致,会直接导致升级失败且原数据目录的数据字典被污染。相关内容将在后续整理后发布到本公众号,欲了解详情,还请持续关注!

References

[1]
 MySQL官方手册: https://dev.mysql.com/doc/refman/8.0/en/upgrade-prerequisites.html
[2]
 1.4 Server and Status Variables and Options Added, Deprecated, or Removed in MySQL 8.0: https://dev.mysql.com/doc/refman/8.0/en/added-deprecated-removed.html
[3]
 Features Removed in MySQL 8.0: https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html#mysql-nutshell-removals


文章转载自腾讯游戏存储与计算技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论