【华为云MySQL技术专栏】MySQL 8.0 数据字典升级源码解析
一、背景
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()函数负责获取mysql和innodb_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_set和remove_set在升级过程中扮演着重要的角色。
当一个表仅包含高版本定义时,说明该表是新增表,需要添加到create_set中;
当一个表仅包含低版本定义时,说明该表是在高版本需要被删除,添加到remove_set中;
当一个表同时存在高版本和低版本不同的定义,说明表结构在升级后发生了变化,则同时添加到create_set和remove_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_set和create_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_keys 和 foreign_key_column_usage 表;
• shared cache重置,包括Collation、Charset、Abstract_table、Event、Routine、Schem、Column_statistics、Spatial_reference_system、Tablespace和Resource_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
- 点赞
- 收藏
- 关注作者
评论(0)