mysql 8.0数据字典简介
数据字典(data dictionary, dd)用来存储数据库内部对象的信息,这些信息也被称为元数据(metadata),包括schema名称、表结构、存储过程的定义等。
图1 mysql 8.0之前的数据字典
图片来源:mysql 8.0 data dictionary: background and motivation
如图1所示,mysql 8.0之前的元数据,分散存储在许多不同的位置,包括各种元数据文件,不支持事务的表和存储引擎特有的数据字典等;server层和存储引擎层有各自的数据字典,其中一部分是重复的。
以上的设计导致支持原子的ddl变得很困难,因此mysql 8.0之前,如果ddl过程中发生crash,后期的恢复很容易出现各种问题,导致表无法访问、复制异常等。
如图2所示,mysql 8.0使用支持事务的innodb存储引擎作来存储元数据,实现数据字典的统一管理。这个改进消除了元数据存储的冗余,通过支持原子ddl,实现了ddl的crash safe。
图2 mysql 8.0数据字典
图片来源:mysql 8.0: data dictionary architecture and design
数据字典表都是隐藏的,只有在debug编译模式下,可以通过设置开关set debug='+d,skip_dd_table_access_check'来直接查看数据字典表。
mysql> set debug='+d,skip_dd_table_access_check'; query ok, 0 rows affected (0.01 sec) mysql> select name, schema_id, hidden, type from mysql.tables where schema_id=1 and hidden='system'; +------------------------------+-----------+--------+------------+ | name | schema_id | hidden | type | +------------------------------+-----------+--------+------------+ | catalogs | 1 | system | base table | | character_sets | 1 | system | base table | | check_constraints | 1 | system | base table | | collations | 1 | system | base table | | column_statistics | 1 | system | base table | | column_type_elements | 1 | system | base table | | columns | 1 | system | base table | | dd_properties | 1 | system | base table | | events | 1 | system | base table | | foreign_key_column_usage | 1 | system | base table | | foreign_keys | 1 | system | base table | | index_column_usage | 1 | system | base table | | index_partitions | 1 | system | base table | | index_stats | 1 | system | base table | | indexes | 1 | system | base table | | innodb_ddl_log | 1 | system | base table | | innodb_dynamic_metadata | 1 | system | base table | | parameter_type_elements | 1 | system | base table | | parameters | 1 | system | base table | | resource_groups | 1 | system | base table | | routines | 1 | system | base table | | schemata | 1 | system | base table | | st_spatial_reference_systems | 1 | system | base table | | table_partition_values | 1 | system | base table | | table_partitions | 1 | system | base table | | table_stats | 1 | system | base table | | tables | 1 | system | base table | | tablespace_files | 1 | system | base table | | tablespaces | 1 | system | base table | | triggers | 1 | system | base table | | view_routine_usage | 1 | system | base table | | view_table_usage | 1 | system | base table | +------------------------------+-----------+--------+------------+ 32 rows in set (0.01 sec)
上面查询得到的表就是隐藏的数据字典表,mysql的元数据存储在这些表中。
在release编译模式下,如果要查看数据字典信息,只能通过information_schema中的视图来查询。例如,可以通过视图information_schema.tables查询数据字典表mysql.tables。
mysql> select table_schema,table_name,table_type,engine -> from information_schema.tables -> where table_schema = 'sbtest' limit 1; +--------------+------------+------------+--------+ | table_schema | table_name | table_type | engine | +--------------+------------+------------+--------+ | sbtest | sbtest1 | base table | innodb | +--------------+------------+------------+--------+ 1 row in set (0.00 sec)
数据字典表的相关代码
数据字典的代码位于sql/dd目录,所有数据字典相关的信息都在dd这个命名空间中,各数据字典表本身的定义位于sql/dd/impl/tables目录的代码中,可以理解为数据字典表的元数据在代码中已经定义好了。
以存储schema信息的schemata表为例,其类的声明如下:
class schemata : public entity_object_table_impl { public: // ... // 所包含的字段 enum enum_fields { field_id, field_catalog_id, field_name, field_default_collation_id, field_created, field_last_altered, field_options, field_default_encryption, field_se_private_data, number_of_fields // always keep this entry at the end of the enum }; // 所包含的索引 enum enum_indexes { index_pk_id = static_cast<uint>(common_index::pk_id), index_uk_catalog_id_name = static_cast<uint>(common_index::uk_name), index_k_default_collation_id }; // 所包含的外键 enum enum_foreign_keys { fk_catalog_id, fk_default_collation_id }; // ... };
其构造函数定义了该表的名称、各字段、索引和外键等信息,以及该表默认存储的数据信息,如下所示:
schemata::schemata() { // 表名 m_target_def.set_table_name("schemata"); // 字段定义 m_target_def.add_field(field_id, "field_id", "id bigint unsigned not null auto_increment"); // ... // 索引定义 m_target_def.add_index(index_pk_id, "index_pk_id", "primary key (id)"); // ... // 外键定义 m_target_def.add_foreign_key(fk_catalog_id, "fk_catalog_id", "foreign key (catalog_id) references \ catalogs(id)"); // ... // 初始化时额外需要执行的dml语句 m_target_def.add_populate_statement( "insert into schemata (catalog_id, name, default_collation_id, created, " "last_altered, options, default_encryption, se_private_data) values " "(1,'information_schema',33, current_timestamp, current_timestamp, " "null, 'no', null)"); }
在初始化和启动时,会使用object_table_definition_impl::get_ddl()函数来获取m_target_def中信息所生成的ddl语句,创建出schemata表;使用object_table_definition_impl::get_dml()获取dml语句,用于初始化表中的数据。
dd::tables::schemata类的继承关系,如图3。所有的数据字典表对应的类,最终都是派生自dd::object_table,便于统一处理。
图3 dd::tables::schemata类
对于这些表中存储的元数据所对应的对象,或者说这些表中的每一行数据所对应的一个对象,比如一个schema、table、column等,代码中也有对应的类。
还是以schema为例,它对应的类是dd::schema,实现类是dd::schema_impl,代表的是schema这种数据库内部对象,也是mysql.schemata表中的一行。
所有数据字典中所存储的对象在代码中的基类都是dd::weak_object,如图4:
图4 dd::schema_impl类
schema的id和name在dd::entity_object_impl中,其他字段在实现类dd::schema_impl中。
实现类dd::schema_impl主要实现了对于元数据对象的各属性的读写访问,与从数据字典中的元数据表schemata的行记录中,存取元数据的接口。
主要相关接口如下:
class weak_object_impl_ : virtual public weak_object { // ... public: // 存储记录到元数据表 virtual bool store(open_dictionary_tables_ctx *otx); // 删除元数据表中的记录 bool drop(open_dictionary_tables_ctx *otx) const; public: // 从元数据表的记录中提取各属性字段 virtual bool restore_attributes(const raw_record &r) = 0; // 保存各属性到元数据表的记录 virtual bool store_attributes(raw_record *r) = 0; // 读取相关对象的信息,如表上的索引等 virtual bool restore_children(open_dictionary_tables_ctx *) { return false; } // 存储相关对象的信息 virtual bool store_children(open_dictionary_tables_ctx *) { return false; } // 删除相关对象的信息 virtual bool drop_children(open_dictionary_tables_ctx *) const { return false; } };
dd::schema_impl主要实现了store_attributes和restore_attributes接口,依据dd::tables::schemata中的表定义信息,读取或存储schema的各个属性信息。
依据以上介绍的,数据字典表的类与数据库内部对象的类,结合innodb存储引擎的接口,实现了对于存储于数据字典各个表中的元数据的读写访问。
例如,存储新建的database的元数据到schema内存对象中:
#0 dd::schema_impl::store_attributes #1 in dd::weak_object_impl::store #2 in dd::cache::storage_adapter::store<dd::schema> #3 in dd::cache::dictionary_client::store<dd::schema> #4 in dd::create_schema #5 in mysql_create_db #6 in mysql_execute_command...
持久化到对应的innodb表mysql.schemata中:
#0 ha_innobase::write_row #1 in handler::ha_write_row #2 in dd::raw_new_record::insert #3 in dd::weak_object_impl::store #4 in dd::cache::storage_adapter::store<dd::schema> #5 in dd::cache::dictionary_client::store<dd::schema> #6 in dd::create_schema #7 in mysql_create_db #8 in mysql_execute_command ...
数据字典的初始化
初始化mysql数据库实例时,即执行mysqld -initialize时,main函数会启动一个bootstrap线程来进行数据字典的初始化,并等待其完成。
数据字典的初始化函数入口是dd::bootstrap::initialize,主要流程如下:
图5 数据字典初始化流程
其中,ddse指的是data dictionary storage engine,数据字典的存储引擎,即innodb。ddse初始化过程主要是对innodb进行必要的初始化,并获取ddse代码中预先定义好的表的定义与表空间的定义。
innodb预定义的数据字典表:
- innodb_dynamic_metadata
innodb的动态元数据,包括表的自增列值等。
- innodb_table_stats
innodb表的统计信息。
- innodb_index_stats
innodb索引的统计信息。
- innodb_ddl_log
存储innodb的ddl日志,用于原子ddl的实现。
innodb预定义的系统表空间:
- mysql
数据字典的表空间,数据字典表都在这个表空间中。
- innodb_system
innodb的系统表空间,主要包含innodb的change buffer;如果不使用file-per-table或指定其他表空间,用户表也会创建在这个表空间中。
innodb的ddse_dict_init接口的实现为innobase_ddse_dict_init,会先调用innobase_init_files初始化所需文件并启动innodb。
主要代码流程如下:
static bool innobase_ddse_dict_init( dict_init_mode_t dict_init_mode, uint, list<const dd::object_table> *tables,list<const plugin_tablespace> *tablespaces) { // ... // 初始化文件并启动innodb if (innobase_init_files(dict_init_mode, tablespaces)) { return true; } // innodb_dynamic_metadata表的定义 dd::object_table *innodb_dynamic_metadata = dd::object_table::create_object_table(); innodb_dynamic_metadata->set_hidden(true); dd::object_table_definition *def = innodb_dynamic_metadata->target_table_definition(); def->set_table_name("innodb_dynamic_metadata"); def->add_field(0, "table_id", "table_id bigint unsigned not null"); def->add_field(1, "version", "version bigint unsigned not null"); def->add_field(2, "metadata", "metadata blob not null"); def->add_index(0, "index_pk", "primary key (table_id)"); // ... /* innodb_table_stats、innodb_index_stats、innodb_ddl_log表的定义 */ // ... }
在ddse初始化并启动的基础上,就可以进行剩下的数据字典初始化过程,主要就是创建数据字典的schema和表。这些表的元数据在执行flush_meta_data时进行持久化。
值得注意的是表mysql.dd_properties,它会存储版本信息等数据字典的属性,还会存储其他数据字典表的定义、id、se_private_data等信息,在数据库启动时使用。
数据字典初始化整体执行的函数调用总结,如图6:
图6 数据字典初始化的函数调用
数据字典的启动
数据字典的启动过程所执行的函数与初始化时十分相似,大部分在函数内部通过opt_initialize全局变量来区分初始化和启动,执行不同的代码逻辑。
与初始化的主要区别是元数据不再需要生成并持久化到存储,而是从存储读取已有的元数据。innodb文件是打开已有的,而不是新建。
数据字典启动的入口是dd::upgrade_57::do_pre_checks_and_initialize_dd。这里虽然有'upgrade_57'这种名称的namespace,但是正常的启动也是从这里开始。
与初始化相同,数据字典的启动也是先准备好ddse,即启动innodb,然后再进行后面启动数据字典的步骤。打开数据字典之前,innodb会进行数据字典的恢复,确保重启前的ddl都正常的提交或回滚,数据字典元数据和数据是处于一致的状态。
dd::upgrade_57::restart_dictionary调用dd::bootstrap::restart,后面的启动步骤由它来实现,主要过程如下。
注意这里的创建表,是创建内存中的对象,不是物理上新创建一个表。这些表的元数据都已经在初始化时持久化了。
bool restart(thd *thd) { bootstrap::dd_bootstrap_ctx::instance().set_stage(bootstrap::stage::started); // 获取预定义的系统tablespace的元数据(mysql和innodb_system) store_predefined_tablespace_metadata(thd); if (create_dd_schema(thd) || // 创建schema:'mysql' initialize_dd_properties(thd) || // 创建mysql.dd_properties表并从中获取版本号等信息 create_tables(thd, nullptr) || // 创建数据字典中其他的表 sync_meta_data(thd) || // 从存储读取数据字典相关的schema、tablespace和表的元数据,进行同步 /* 打开innodb的数据字典表(innodb_dynamic_metadata, innodb_table_stats, innodb_index_stats, innodb_ddl_log),加载所有innodb的表空间 */ ddse_dict_recover(thd, dict_recovery_restart_server, d->get_actual_dd_version(thd)) || upgrade::do_server_upgrade_checks(thd) || // 检查是否能够升级(如果需要的话,正常启动不涉及) upgrade::upgrade_tables(thd) || // 升级数据字典表的定义及其中的元数据(如果需要的话,正常启动不涉及) repopulate_charsets_and_collations(thd) || // 更新charset和collation信息 verify_contents(thd) || // 验证数据字典内容 update_versions(thd, false)) { // 更新版本信息到dd_properties表 return true; } // ... bootstrap::dd_bootstrap_ctx::instance().set_stage(bootstrap::stage::finished); logerr(information_level, er_dd_version_found, d->get_actual_dd_version(thd)); return false; }
启动时各个数据字典表的根页面信息是从 mysql.dd_properties表中获取的,通过该页面可以访问对应表的所有数据。
mysql.dd_properties表的根页面是固定的,并且它里面保存了数组字典表本身的元数据。相关函数:dd::get_se_private_data()。
小结
mysql 8.0新设计实现的数据字典,解决了之前版本的数据字典冗余,ddl原子性、crash safe等问题。通过对数据字典的初始化流程,以及数据字典正常重启时加载流程的梳理,希望读者对新数据字典的实现和运行有一个更深入的了解。
以上就是mysql 8.0数据字典的初始化与启动流程的详细内容,更多关于mysql 8.0数据字典的资料请关注代码网其它相关文章!
发表评论