【华为云MySQL技术专栏】MySQL 8.0 数据字典升级源码解析

举报
GaussDB 数据库 发表于 2024/11/13 16:55:55 2024/11/13
【摘要】 一、背景MySQL 8.0版本引入了全局统一的数据字典(Data Dictionary, DD),通过集中、高效、事务性的方式管理元数据(如表、列、索引等定义),不仅简化了管理流程,也对性能和并发性有较大的帮助。此外,升级到MySQL 8.0版本后,当涉及数据库中元数据对象(如表、索引、视图或存储过程等)发生定义更改或者某些新特性涉及对系统表的修改等情况时,都可能需要更新数据字典以提供支持。...

MySQL顶部banner.jpg

一、背景

MySQL 8.0版本引入了全局统一的数据字典(Data Dictionary, DD),通过集中、高效、事务性的方式管理元数据(如表、列、索引等定义),不仅简化了管理流程,也对性能和并发性有较大的帮助。

此外,升级到MySQL 8.0版本后,当涉及数据库中元数据对象(如表、索引、视图或存储过程等)发生定义更改或者某些新特性涉及对系统表的修改等情况时,都可能需要更新数据字典以提供支持。

因此,无论是从MySQL 5.x版本升级到8.0版本,还是在8.x版本间的演进,只要涉及到数据字典的更新,都会伴随着数据字典的升级操作。

本文将重点介绍MySQL 8.x版本间的数据字典表和剩余系统表的升级步骤和升级逻辑。

二、升级步骤和参数[1]

当需要将低版本的MySQL升级到高版本时,以下模块涉及升级过程。

1. mysql schema中包含的系统表(存储mysql server运行所需的信息),包括如下两大类。

数据字典表:存储表、列、索引等数据库元数据信息。

剩余系统表:即非DD的用于其他操作用途的系统表。

2. 其他内置的schema,包括performance_schema、information_schema、ndbinfo和sys schemas以及用户schema。

针对不同的升级内容,MySQL 8.0版本中引入了DD版本号来看护DD的升级,同时,采用server版本号(即MySQL版本号)来看护非DD系统表和其他schema中对象的升级。

步骤一:数据字典升级

这一阶段会完成mysql schema中的DD表、performance schema、information_schema和ndbinfo的升级。

步骤二:server升级

这一阶段会完成mysql schema中非DD表、sys_schema和用户schema的升级。MySQL 8.0.16及之后的版本引入--upgrade参数,用于控制升级的范围。

升级参数

数据字典升级和server升级

AUTO (缺省时默认)

数据字典升级和server升级自动识别所有需要升级的内容。

NONE

都不升级。但是当数据字典必须升级时,会失败退出。

MINIMAL

升级必要的数据字典、performance schema和information schema

FORCE

升级必要的数据字典、performance schema和information schema;强制升级步骤二中剩余的所有升级。

表1. MySQL 升级参数

下面将介绍数据字典和相关系统表的升级过程,不涉及IS/PS/用户表的处理。

三、数据字典升级代码介绍

3.1 数据字典DD启动&升级函数调用总

数据字典的启动和升级在同一个流程中,通过校验相关的版本号和升级参数,来决策是否进入升级相关的分支。其统一的入口都是dd::upgrade_57::do_pre_checks_and_initialize_dd()。在进入升级阶段前,整体的处理流程是一致的,这部分内容已在《【华为云 MySQL技术专栏】MySQL 8.0数据字典的初始化与启动 》进行了介绍,此处不再赘述。

图1展示了在启动阶段DD,数据字典升级所涉及的核心函数调用,其中upgrade_tables部分即为升级部分的函数调用。整体一览,可以看出,在DD升级时,会先按照正常的启动流程加载现有的DD,然后再进入升级流程,并最终完成升级。这部分在下面会详细展开。

图1 DD启动&升级的函数调用

3.2 数据字典升级流程介绍

3.2.1 升级的依据——版本号

MySQL中定义了server(服务器)的版本号(MYSQL_VERSION_ID)和数据字典的版本号(DD_VERSION),这两个版本号用于在判断是否需要升级时进行比对。

MYSQL_VERSION_ID

MySQL基础版本号,用于检测标识MySQL server是否需要升级;

bool is_server_upgrade() const {
  return !opt_initialize && (m_upgraded_server_version < MYSQL_VERSION_ID);
}

DD_VERSION

数据字典版本号,用于检测标识数据字典是否需要升级;

bool is_dd_upgrade() const {
  return !opt_initialize && (m_actual_dd_version < dd::DD_VERSION);
}

当然,对于I_S和PS也定义了相关版本号,但是,这两个版本号不是决定server或者DD升级的依据。

3.2.2 数据字典升级的核心技巧

为了便于理解DD升级的过程,这里先对DD升级的核心技巧做一个必要的介绍。

图2 启动&升级过程中的表处理

如图2所示,在启动流程中,仅涉及步骤1,即在mysql schema中,根据当前的表定义,直接创建出相关的DD表。

在升级流程中,为了防止升级过程中异常导致不可恢复,引入了两个临时schema:actual schema(存放当前定义,即低版本定义的表)和target schema(存放高版本定义的表),用以保证数据字典升级过程中的原子性。具体步骤如下:

1)先在mysql schema中,创建出当前低版本定义的表;

2)在target schema中创建高版本定义的表,然后基于高低版本之间的差异,在target schema中完成对高版本DD表的数据填充;

3)将发生更改或者要移除的旧DD表,从mysql schema移动到actual schema中,待后续删除;

4)将完整的新DD表从target schema中迁回到mysql schema中,完成升级。这样,即使在升级过程中出现了异常中断,也可以保证整个升级流程的原子性。

3.2.3 数据字典启动与升级相关逻辑

虽然DD升级和启动在同一个流程中,但是对于升级场景,启动过程会为升级额外做部分准备工作。下面是对升级场景下DD启动流程的一个简要回顾。

restart
// 获取预定义的系统tablespace的元数据(mysql和innodb_system)
|----store_predefined_tablespace_metadata 
// 创建schema:'mysql'
|----create_dd_schema 
// 1、创建mysql.dd_properties表并从中获取版本号等信息, 2、生成低版本actual的表定义
|----initialize_dd_properties 
// 使用actual表定义创建相关系统表
|----create_tables 
// 从存储读取数据字典相关的schema、tablespace和表的元数据,进行同步
|----sync_meta_data 
// 打开InnoDB的数据字典表(innodb_dynamic_metadata, innodb_table_stats, innodb_index_stats, innodb_ddl_log),加载所有InnoDB的表空间
|----DDSE_dict_recover 
// 升级前检查
|----do_server_upgrade_checks 
// 进行DD表升级和元数据更新
|----upgrade_tables 
// 更新charset和collation信息
|----repopulate_charsets_and_collations 
// 验证数据字典内容
|----verify_contents 
// 更新版本信息到dd_properties表
|----update_versions

说明:

store_predefined_tablespace_metadata()函数负责获取mysqlinnodb_system的tablespace,用于创建相关的系统表。

initialize_dd_properties阶段,相比一般的启动流程,还会额外生成现有(actual,即低版本)数据字典表的表定义。在后面的create_tables阶段,会用此表定义去创建相关的低版本表。

do_server_upgrade_checks的主要功能实际上是检查是否满足升级的条件,所以,不止在升级流程中执行,在MySQL 8.x版本间的正常启动时,也会执行。这里主要包括:

1)通用检查:检查tables、events、routines、views的语法错误;

2)定制化检查:比如,路径包含80013时,会检查分区表不能存储在共享表空间。

3.2.4 数据字典升级过程解析

数据字典升级从过程上可分为三个部分:

1)创建临时schema和DD表,并进行数据迁移;

2)切换DD表到mysql schema,并更新相关元数据和缓存;

3)使用升级后的数据重新启动DD。在升级过程中,通过引入临时的actual和target schema进行数据中转,保障了升级过程中的原子性。

具体代码分析如下:

1)创建临时schema和DD表并进行数据迁移

upgrade_tables
// 根据版本号检查是否需要升级DD
|----is_dd_upgrade 
// 存放待删除的低版本表
|----std::set<String_type> remove_set = {}; 
// 存放待创建的高版本表
|----std::set<String_type> create_set = {}; 
// 创建target schema(高版本)和actual schema(低版本)
|----create_temporary_schemas 
// 通过检查bootstrap::Stage,识别需要在高版本schema中创建高版本表
|----create_tables 
// 在mysql schema上使用DDL语句进行元数据更新
|----update_meta_data 
// 将更新后的DD表的数据表中迁移到高版本schema对应的表中
|----migrate_meta_data 

说明:

create_setremove_set在升级过程中扮演着重要的角色。

一个表仅包含高版本定义时,说明该表是新增表,需要添加到create_set中;

一个表仅包含低版本定义时,说明该表是在高版本需要被删除,添加到remove_set中;

当一个表同时存在高版本和低版本不同的定义,说明表结构在升级后发生了变化,则同时添加到create_setremove_set中。

upgrade_meta_data()函数中,仅对位于mysql schema中DD表使用DML语句进行更新操作,执行类似“UPDATE mysql.tables SET created = CURRENT_TIMESTAMP WHERE created = 0”的SQL 语句;

migrate_meta_data()过程中,迁移涉及的表包括:

(1)表定义发生修改的DD表,这些表的定义同时存在于remove_setcreate_set中;

(2)针对某些升级定制化的DD表。其中,通过执行DML语句,将mysql schema中更新后的DD表数据插入到高版本schema对应的表中,这类DML语句是“INSERT INTO table1 SELECT *,0 FROM mysql.table1”。

可以看出,表越多、表定义越复杂(比如包含全文索引等),需要迁移的数据量就越大,耗时也就会越长。对于某些大型数据库,其迁移时长可能长达几十分钟或更长。

2)切换DD表到mysql schema并更新相关元数据和缓存

// 使用更新后的数据刷新DD_propertie表的相关属性
|---- update_properties 
// 使用DML语句将create_set中的table及其外键从高版本schema中更新到mysql schema;将remove set中的table从mysqlschema更新到低版本schema,并删除其外键和外键列的信息.
|---- update_object_ids 
// 更新版本号,结束DD事务
|---- update_versions 
// 重置shared cache
|---- dd::cache::Shared_dictionary_cache::instance()->reset(false) 
// 更新DD引擎(innodb)的cache
|---- ddse->dict_cache_reset(MYSQL_SCHEMA_NAME.str, 
                             (*it)->entity()->name().c_str());
        ddse->dict_cache_reset(target_table_schema_name.c_str(),
                               (*it)->entity()->name().c_str());

说明:

upgrade_object_ids()函数中,采用直接修改DD tables表的schema id的方式来实现schema的更改。这一修改直接影响tables、foreign_keysforeign_key_column_usage

shared cache重置包括CollationCharsetAbstract_tableEventRoutineSchemColumn_statisticsSpatial_reference_systemTablespaceResource_group这些DD对象。

3)以升级后的数据重新启动DD

// 设置状态为STARTED
|----bootstrap::DD_bootstrap_ctx::instance().set_stage(
     bootstrap::Stage::STARTED) 
// 获取预定义的系统tablespace的元数据(mysql和innodb_system)
|----store_predefined_tablespace_metadata(thd); 
// 使用升级后的数据重新进行DD启动流程
|----create_dd_schema(thd) || initialize_dd_properties(thd) ||
     create_tables(thd, nullptr) || sync_meta_data(thd)  

说明:

将状态设置为bootstrap::Stage::STARTED是很有必要的。在执行DDL语句(如创建schema等操作)时,系统会进行相关的校验。如果未设置这个状态,那么在后续的create_dd_schema中,由于在3.2.3节中的启动流程中已经创建了mysql schema,此时系统就会报错,提示mysql schema已存在,导致操作失败。

下面展示附部分校验相关的代码

bool Storage_adapter::get() {
  ...
/* We may have a cache miss while checking for existing tables during server start. At this stage, the object will be considered not existing. */
  if (bootstrap::DD_bootstrap_ctx::instance().get_stage() <
      bootstrap::Stage::CREATED_TABLES)
    return false;
  ...
}
bool Storage_adapter::store(THD *thd, T *object) {
  if (s_use_fake_storage ||
      bootstrap::DD_bootstrap_ctx::instance().get_stage() <
          bootstrap::Stage::CREATED_TABLES) {
    instance()->core_store(thd, object);
    return false;
  }
  ...
}

sync_meta_data过程中,相比3.2.3节中的启动流程,会额外清除在升级过程中创建的临时target schema(高版本)actual schema(低版本),完成对临时schema的清理。

3.3 涉及的非DD系统表的处理

在《【华为云MySQL技术专栏】MySQL 8.0数据字典的初始化与启动 》中介绍了哪些系统表是数据字典表,可以通过在debug编译模式下,设置开关

set debug='+d,skip_dd_table_access_check';

然后执行如下语句来进行获取,

SELECT name, schema_id, hidden, type 
FROM mysql.tables 
where schema_id=1 
  AND hidden='System';

剩余的系统表即为非DD的系统表。当某些特性除了修改DD表进行了元数据的更改外,还对非DD的系统表进行了适配,比如,某个特性添加或者删除了某个用于权限的字段,但是仅升级DD表而不处理非DD系统表,就会导致访问该权限字段时出错。接下来简要介绍下非DD系统表的升级处理。

非DD系统表升级的核心是在dd::upgrade::upgrade_system_schemas中,运行预置SQL语句来进行相关系统表的处理,涉及执行包含SQL语句的文件:

1)mysql_system_tables.sql;

2)mysql_system_tables_fix.sql;

3)mysql_system_tables_data_fix.sql;

4)mysql_sys_schema.sql。

其在升级流程中的位置如下。

#0 dd::upgrade::upgrade_system_schemas at sql/sql/dd/impl/ugrade/server.cc
#1 init_server_components at sql/sql/mysqld.cc

对于SQL语句的加载是在CMakeLists.txt中进行处理的,比如对于mysql_system_tables.sql的处理,先生成mysql_fix_privilege_tables.sql,

SET(CAT_COMMAND COMMAND
    ${CMAKE_COMMAND} -E chdir  ${CMAKE_CURRENT_SOURCE_DIR}
    ${CAT_EXECUTABLE} mysql_system_tables.sql mysql_system_tables_fix.sql >
    ${CMAKE_CURRENT_BINARY_DIR}/mysql_fix_privilege_tables.sql
  )

再生成mysql_fix_privilege_tables.h,并将DDL 加载到mysql_fix_privilege_tables变量中。

# Build mysql_fix_privilege_tables.h
ADD_CUSTOM_COMMAND(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mysql_fix_privilege_tables_sql.h
  ${CAT_COMMAND}
  COMMAND comp_sql
  mysql_fix_privilege_tables
  mysql_fix_privilege_tables.sql
  mysql_fix_privilege_tables_sql.h
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
  DEPENDS comp_sql
  ${CMAKE_CURRENT_SOURCE_DIR}/mysql_system_tables.sql
  ${CMAKE_CURRENT_SOURCE_DIR}/mysql_system_tables_fix.sql
)

最终,在upgrade_system_schemas()函数中,通过for循环遍历并执行文件中的SQL语句,实现对非DD表的更新。

upgrade_system_schemas(THD *thd)
|----fix_mysql_tables
    // 运行mysql_system_tables.sql和mysql_system_tables_fix.sql中的语句
    |----遍历执行mysql_fix_privilege_tables中的语句 
    // 运行mysql_system_tables_data_fix.sql中的语句
    |----遍历执行mysql_system_tables_data_fix中的语句 
|----fix_sys_schema
    // 运行mysql_sys_schema.sql中的语句
    |----遍历执行mysql_sys_schema中的语句 

其中,

mysql_system_tables.sql创建相关系统表

mysql_system_tables_fix.sql:对相关系统表使用UPDATE语句进行更新

mysql_system_tables_data_fix.sql填充更新系统表数据,当前仅插入或更新mysql.st_spatial_reference_systems表中的数据

mysql_sys_schema.sql创建或更新sys schema。该文件是将scripts/sys_schema/路径下的相关SQL文件最终整合成了一个统一mysql_sys_schema.sql文件。

四、

当数据库中的元数据对象定义发生更改等情况时,需要数据字典升级来适配相关的变化。在数据字典升级的过程中,核心是引入两个临时schema,分别用于存放新旧版本的DD表,然后在临时schema中对相关DD表进行数据更新,以此来确保整体升级过程的原子性。考虑到某些特性还需要非DD系统表的升级配合,通过执行预置的一系列SQL语句进行创建、更新来完成其升级。

通过本文的介绍,希望读者对数据字典表升级的内部逻辑和代码有进一步的理解,后续会继续探究MySQL 8.0版本启动和升级过程中IS/PS/用户表的处理,敬请期待。

[1]. https://dev.mysql.com/doc/refman/8.0/en/upgrading-what-is-upgraded.html

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。