MySQL 8.0數(shù)據(jù)字典的初始化與啟動(dòng)流程
MySQL 8.0數(shù)據(jù)字典簡(jiǎn)介
數(shù)據(jù)字典(Data Dictionary, DD)用來(lái)存儲(chǔ)數(shù)據(jù)庫(kù)內(nèi)部對(duì)象的信息,這些信息也被稱(chēng)為元數(shù)據(jù)(Metadata),包括schema名稱(chēng)、表結(jié)構(gòu)、存儲(chǔ)過(guò)程的定義等。

圖1 MySQL 8.0之前的數(shù)據(jù)字典
圖片來(lái)源:MySQL 8.0 Data Dictionary: Background and Motivation
如圖1所示,MySQL 8.0之前的元數(shù)據(jù),分散存儲(chǔ)在許多不同的位置,包括各種元數(shù)據(jù)文件,不支持事務(wù)的表和存儲(chǔ)引擎特有的數(shù)據(jù)字典等;Server層和存儲(chǔ)引擎層有各自的數(shù)據(jù)字典,其中一部分是重復(fù)的。
以上的設(shè)計(jì)導(dǎo)致支持原子的DDL變得很困難,因此MySQL 8.0之前,如果DDL過(guò)程中發(fā)生crash,后期的恢復(fù)很容易出現(xiàn)各種問(wèn)題,導(dǎo)致表無(wú)法訪問(wèn)、復(fù)制異常等。
如圖2所示,MySQL 8.0使用支持事務(wù)的InnoDB存儲(chǔ)引擎作來(lái)存儲(chǔ)元數(shù)據(jù),實(shí)現(xiàn)數(shù)據(jù)字典的統(tǒng)一管理。這個(gè)改進(jìn)消除了元數(shù)據(jù)存儲(chǔ)的冗余,通過(guò)支持原子DDL,實(shí)現(xiàn)了DDL的crash safe。

圖2 MySQL 8.0數(shù)據(jù)字典
圖片來(lái)源:MySQL 8.0: Data Dictionary Architecture and Design
數(shù)據(jù)字典表都是隱藏的,只有在debug編譯模式下,可以通過(guò)設(shè)置開(kāi)關(guān)set debug='+d,skip_dd_table_access_check'來(lái)直接查看數(shù)據(jù)字典表。
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)
上面查詢得到的表就是隱藏的數(shù)據(jù)字典表,MySQL的元數(shù)據(jù)存儲(chǔ)在這些表中。
在release編譯模式下,如果要查看數(shù)據(jù)字典信息,只能通過(guò)INFORMATION_SCHEMA中的視圖來(lái)查詢。例如,可以通過(guò)視圖information_schema.tables查詢數(shù)據(jù)字典表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)
數(shù)據(jù)字典表的相關(guān)代碼
數(shù)據(jù)字典的代碼位于sql/dd目錄,所有數(shù)據(jù)字典相關(guān)的信息都在dd這個(gè)命名空間中,各數(shù)據(jù)字典表本身的定義位于sql/dd/impl/tables目錄的代碼中,可以理解為數(shù)據(jù)字典表的元數(shù)據(jù)在代碼中已經(jīng)定義好了。
以存儲(chǔ)schema信息的schemata表為例,其類(lèi)的聲明如下:
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 };
// ...
};
其構(gòu)造函數(shù)定義了該表的名稱(chēng)、各字段、索引和外鍵等信息,以及該表默認(rèn)存儲(chǔ)的數(shù)據(jù)信息,如下所示:
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)");
// ...
// 初始化時(shí)額外需要執(zhí)行的DML語(yǔ)句
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)");
}
在初始化和啟動(dòng)時(shí),會(huì)使用Object_table_definition_impl::get_ddl()函數(shù)來(lái)獲取m_target_def中信息所生成的DDL語(yǔ)句,創(chuàng)建出schemata表;使用Object_table_definition_impl::get_dml()獲取DML語(yǔ)句,用于初始化表中的數(shù)據(jù)。
dd::tables::Schemata類(lèi)的繼承關(guān)系,如圖3。所有的數(shù)據(jù)字典表對(duì)應(yīng)的類(lèi),最終都是派生自dd::Object_table,便于統(tǒng)一處理。

圖3 dd::tables::Schemata類(lèi)
對(duì)于這些表中存儲(chǔ)的元數(shù)據(jù)所對(duì)應(yīng)的對(duì)象,或者說(shuō)這些表中的每一行數(shù)據(jù)所對(duì)應(yīng)的一個(gè)對(duì)象,比如一個(gè)schema、table、column等,代碼中也有對(duì)應(yīng)的類(lèi)。
還是以schema為例,它對(duì)應(yīng)的類(lèi)是dd::Schema,實(shí)現(xiàn)類(lèi)是dd::Schema_impl,代表的是schema這種數(shù)據(jù)庫(kù)內(nèi)部對(duì)象,也是mysql.schemata表中的一行。
所有數(shù)據(jù)字典中所存儲(chǔ)的對(duì)象在代碼中的基類(lèi)都是dd::Weak_object,如圖4:

圖4 dd::Schema_impl類(lèi)
schema的id和name在dd::Entity_object_impl中,其他字段在實(shí)現(xiàn)類(lèi)dd::Schema_impl中。
實(shí)現(xiàn)類(lèi)dd::Schema_impl主要實(shí)現(xiàn)了對(duì)于元數(shù)據(jù)對(duì)象的各屬性的讀寫(xiě)訪問(wèn),與從數(shù)據(jù)字典中的元數(shù)據(jù)表schemata的行記錄中,存取元數(shù)據(jù)的接口。
主要相關(guān)接口如下:
class Weak_object_impl_ : virtual public Weak_object {
// ...
public:
// 存儲(chǔ)記錄到元數(shù)據(jù)表
virtual bool store(Open_dictionary_tables_ctx *otx);
// 刪除元數(shù)據(jù)表中的記錄 bool drop(Open_dictionary_tables_ctx *otx) const;
public:
// 從元數(shù)據(jù)表的記錄中提取各屬性字段
virtual bool restore_attributes(const Raw_record &r) = 0;
// 保存各屬性到元數(shù)據(jù)表的記錄
virtual bool store_attributes(Raw_record *r) = 0;
// 讀取相關(guān)對(duì)象的信息,如表上的索引等
virtual bool restore_children(Open_dictionary_tables_ctx *) { return false; }
// 存儲(chǔ)相關(guān)對(duì)象的信息
virtual bool store_children(Open_dictionary_tables_ctx *) { return false; }
// 刪除相關(guān)對(duì)象的信息
virtual bool drop_children(Open_dictionary_tables_ctx *) const {
return false;
}
};
dd::Schema_impl主要實(shí)現(xiàn)了store_attributes和restore_attributes接口,依據(jù)dd::tables::Schemata中的表定義信息,讀取或存儲(chǔ)schema的各個(gè)屬性信息。
依據(jù)以上介紹的,數(shù)據(jù)字典表的類(lèi)與數(shù)據(jù)庫(kù)內(nèi)部對(duì)象的類(lèi),結(jié)合InnoDB存儲(chǔ)引擎的接口,實(shí)現(xiàn)了對(duì)于存儲(chǔ)于數(shù)據(jù)字典各個(gè)表中的元數(shù)據(jù)的讀寫(xiě)訪問(wèn)。
例如,存儲(chǔ)新建的database的元數(shù)據(jù)到schema內(nèi)存對(duì)象中:
#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...
持久化到對(duì)應(yīng)的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 ...
數(shù)據(jù)字典的初始化
初始化MySQL數(shù)據(jù)庫(kù)實(shí)例時(shí),即執(zhí)行mysqld -initialize時(shí),main函數(shù)會(huì)啟動(dòng)一個(gè)bootstrap線程來(lái)進(jìn)行數(shù)據(jù)字典的初始化,并等待其完成。
數(shù)據(jù)字典的初始化函數(shù)入口是dd::bootstrap::initialize,主要流程如下:

圖5 數(shù)據(jù)字典初始化流程
其中,DDSE指的是Data Dictionary Storage Engine,數(shù)據(jù)字典的存儲(chǔ)引擎,即InnoDB。DDSE初始化過(guò)程主要是對(duì)InnoDB進(jìn)行必要的初始化,并獲取DDSE代碼中預(yù)先定義好的表的定義與表空間的定義。
InnoDB預(yù)定義的數(shù)據(jù)字典表:
- innodb_dynamic_metadata
InnoDB的動(dòng)態(tài)元數(shù)據(jù),包括表的自增列值等。
- innodb_table_stats
InnoDB表的統(tǒng)計(jì)信息。
- innodb_index_stats
InnoDB索引的統(tǒng)計(jì)信息。
- innodb_ddl_log
存儲(chǔ)InnoDB的DDL日志,用于原子DDL的實(shí)現(xiàn)。
InnoDB預(yù)定義的系統(tǒng)表空間:
- mysql
數(shù)據(jù)字典的表空間,數(shù)據(jù)字典表都在這個(gè)表空間中。
- innodb_system
InnoDB的系統(tǒng)表空間,主要包含InnoDB的Change Buffer;如果不使用file-per-table或指定其他表空間,用戶表也會(huì)創(chuàng)建在這個(gè)表空間中。
InnoDB的ddse_dict_init接口的實(shí)現(xiàn)為innobase_ddse_dict_init,會(huì)先調(diào)用innobase_init_files初始化所需文件并啟動(dòng)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) {
// ...
// 初始化文件并啟動(dòng)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初始化并啟動(dòng)的基礎(chǔ)上,就可以進(jìn)行剩下的數(shù)據(jù)字典初始化過(guò)程,主要就是創(chuàng)建數(shù)據(jù)字典的schema和表。這些表的元數(shù)據(jù)在執(zhí)行flush_meta_data時(shí)進(jìn)行持久化。
值得注意的是表mysql.dd_properties,它會(huì)存儲(chǔ)版本信息等數(shù)據(jù)字典的屬性,還會(huì)存儲(chǔ)其他數(shù)據(jù)字典表的定義、id、se_private_data等信息,在數(shù)據(jù)庫(kù)啟動(dòng)時(shí)使用。
數(shù)據(jù)字典初始化整體執(zhí)行的函數(shù)調(diào)用總結(jié),如圖6:

圖6 數(shù)據(jù)字典初始化的函數(shù)調(diào)用
數(shù)據(jù)字典的啟動(dòng)
數(shù)據(jù)字典的啟動(dòng)過(guò)程所執(zhí)行的函數(shù)與初始化時(shí)十分相似,大部分在函數(shù)內(nèi)部通過(guò)opt_initialize全局變量來(lái)區(qū)分初始化和啟動(dòng),執(zhí)行不同的代碼邏輯。
與初始化的主要區(qū)別是元數(shù)據(jù)不再需要生成并持久化到存儲(chǔ),而是從存儲(chǔ)讀取已有的元數(shù)據(jù)。InnoDB文件是打開(kāi)已有的,而不是新建。
數(shù)據(jù)字典啟動(dòng)的入口是dd::upgrade_57::do_pre_checks_and_initialize_dd。這里雖然有'upgrade_57'這種名稱(chēng)的namespace,但是正常的啟動(dòng)也是從這里開(kāi)始。
與初始化相同,數(shù)據(jù)字典的啟動(dòng)也是先準(zhǔn)備好DDSE,即啟動(dòng)InnoDB,然后再進(jìn)行后面啟動(dòng)數(shù)據(jù)字典的步驟。打開(kāi)數(shù)據(jù)字典之前,InnoDB會(huì)進(jìn)行數(shù)據(jù)字典的恢復(fù),確保重啟前的DDL都正常的提交或回滾,數(shù)據(jù)字典元數(shù)據(jù)和數(shù)據(jù)是處于一致的狀態(tài)。
dd::upgrade_57::restart_dictionary調(diào)用dd::bootstrap::restart,后面的啟動(dòng)步驟由它來(lái)實(shí)現(xiàn),主要過(guò)程如下。
注意這里的創(chuàng)建表,是創(chuàng)建內(nèi)存中的對(duì)象,不是物理上新創(chuàng)建一個(gè)表。這些表的元數(shù)據(jù)都已經(jīng)在初始化時(shí)持久化了。
bool restart(THD *thd) {
bootstrap::DD_bootstrap_ctx::instance().set_stage(bootstrap::Stage::STARTED);
// 獲取預(yù)定義的系統(tǒng)tablespace的元數(shù)據(jù)(mysql和innodb_system)
store_predefined_tablespace_metadata(thd);
if (create_dd_schema(thd) || // 創(chuàng)建schema:'mysql'
initialize_dd_properties(thd) || // 創(chuàng)建mysql.dd_properties表并從中獲取版本號(hào)等信息
create_tables(thd, nullptr) || // 創(chuàng)建數(shù)據(jù)字典中其他的表
sync_meta_data(thd) || // 從存儲(chǔ)讀取數(shù)據(jù)字典相關(guān)的schema、tablespace和表的元數(shù)據(jù),進(jìn)行同步
/* 打開(kāi)InnoDB的數(shù)據(jù)字典表(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) || // 檢查是否能夠升級(jí)(如果需要的話,正常啟動(dòng)不涉及)
upgrade::upgrade_tables(thd) || // 升級(jí)數(shù)據(jù)字典表的定義及其中的元數(shù)據(jù)(如果需要的話,正常啟動(dòng)不涉及)
repopulate_charsets_and_collations(thd) || // 更新charset和collation信息
verify_contents(thd) || // 驗(yàn)證數(shù)據(jù)字典內(nèi)容
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;
}
啟動(dòng)時(shí)各個(gè)數(shù)據(jù)字典表的根頁(yè)面信息是從 mysql.dd_properties表中獲取的,通過(guò)該頁(yè)面可以訪問(wèn)對(duì)應(yīng)表的所有數(shù)據(jù)。
mysql.dd_properties表的根頁(yè)面是固定的,并且它里面保存了數(shù)組字典表本身的元數(shù)據(jù)。相關(guān)函數(shù):dd::get_se_private_data()。
小結(jié)
MySQL 8.0新設(shè)計(jì)實(shí)現(xiàn)的數(shù)據(jù)字典,解決了之前版本的數(shù)據(jù)字典冗余,DDL原子性、crash safe等問(wèn)題。通過(guò)對(duì)數(shù)據(jù)字典的初始化流程,以及數(shù)據(jù)字典正常重啟時(shí)加載流程的梳理,希望讀者對(duì)新數(shù)據(jù)字典的實(shí)現(xiàn)和運(yùn)行有一個(gè)更深入的了解。
以上就是MySQL 8.0數(shù)據(jù)字典的初始化與啟動(dòng)流程的詳細(xì)內(nèi)容,更多關(guān)于MySQL 8.0數(shù)據(jù)字典的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mysql lpad函數(shù)和rpad函數(shù)的使用詳解
MySQL中的LPAD和RPAD函數(shù)用于字符串填充,LPAD從左至右填充,RPAD從右至左填充,兩者都可指定填充長(zhǎng)度和填充字符,如果填充長(zhǎng)度小于原字符串長(zhǎng)度,則會(huì)截取原字符串相應(yīng)長(zhǎng)度的字符2025-02-02
安裝配置MySQLMTOP來(lái)監(jiān)控MySQL運(yùn)行性能的教程
這篇文章主要介紹了安裝配置MySQLMTOP來(lái)監(jiān)控MySQL運(yùn)行性能的教程,MySQLMTOP具有B/S方式的圖形化操作頁(yè)面,需要的朋友可以參考下2015-12-12
MySQL數(shù)據(jù)庫(kù)索引原理及優(yōu)化策略
MySQL數(shù)據(jù)庫(kù)索引是一種數(shù)據(jù)結(jié)構(gòu),用于提高數(shù)據(jù)查詢的效率,加快數(shù)據(jù)檢索的速度。索引基于樹(shù)結(jié)構(gòu)實(shí)現(xiàn),可以通過(guò)B+樹(shù)等算法來(lái)優(yōu)化索引效率。MySQL中常見(jiàn)的索引類(lèi)型包括主鍵索引、唯一索引、普通索引、全文索引等2023-04-04
mysql優(yōu)化連接數(shù)防止訪問(wèn)量過(guò)高的方法
這篇文章主要介紹了mysql優(yōu)化連接數(shù)防止訪問(wèn)量過(guò)高的方法,需要的朋友可以參考下2014-06-06

