|
MySQL 8.0 對(duì)數(shù)據(jù)字典進(jìn)行了重構(gòu),用戶表、數(shù)據(jù)字典表、MySQL 其它系統(tǒng)表的元數(shù)據(jù)都統(tǒng)一保存到 mysql 庫(kù)的數(shù)據(jù)字典表中了。 mysql 庫(kù)中,除了 general_log、slow_log 2 個(gè)日志表,其它所有表的存儲(chǔ)引擎都是 InnoDB,伴隨而來(lái)的是 DDL 終于能夠支持原子操作了。 以 DROP TABLE t1, t2 為例,不會(huì)出現(xiàn) t1 表刪除成功,t2 表刪除失敗的情況,而是要么都刪除成功,要么都刪除失敗。 本文我們就來(lái)聊聊 MySQL 8.0 中的數(shù)據(jù)字典表。
目錄
正文 1. 概述MySQL 8.0 重構(gòu)數(shù)據(jù)字典之后,廢除了 MySQL 5.7 中用于保存元數(shù)據(jù)的磁盤文件:.frm、.par、.TRN、.TRG、.isl、db.opt、ddl_log.log。
這些文件被廢除之后,原本保存到這些文件中的元數(shù)據(jù),都保存到數(shù)據(jù)字典表中了。 數(shù)據(jù)字典表本身也大變樣了:
2. 數(shù)據(jù)字典表有哪些?按照官方文檔的定義,MySQL 8.0 一共有 31 張數(shù)據(jù)字典表: dd_propertiesinnodb_ddl_logcatalogscharacter_setscheck_constraintscollationscolumn_statisticscolumn_type_elementscolumnseventsforeign_key_column_usageforeign_keysindex_column_usageindex_partitionsindex_statsindexesparameter_type_elementsparametersresource_groupsroutinesschematast_spatial_reference_systemstable_partition_valuestable_partitionstable_statstablestablespace_filestablespacestriggersview_routine_usageview_table_usage上面只是簡(jiǎn)單列出了數(shù)據(jù)字典表的表名,如果想了解每個(gè)表存放了什么內(nèi)容,可以參照官方文檔:https://dev./doc/refman/8.0/en/system-schema.html 默認(rèn)情況下,我們是看不到數(shù)據(jù)字典表的,需要滿足以下條件才能看到:
滿足以上 2 個(gè)條件之后,執(zhí)行下面這條 SQL 就可以看到所有數(shù)據(jù)字典表了: SELECT a.name AS db_name, b.*FROM mysql.schemata AS aINNER JOIN mysql.tables AS b ON a.id = b.schema_idWHERE b.schema_id = 1 AND b.hidden = 'System'ORDER BY b.id
上面列出的數(shù)據(jù)字典表中,有 4 個(gè)需要重點(diǎn)介紹,因?yàn)椴还苁菙?shù)據(jù)字典表本身,還是用戶表,都離不開(kāi)這 4 個(gè)表:
index_column_usage 和 SYS_FIELDS 表不完全一樣,有 2 點(diǎn)需要說(shuō)明:
![]() index_id = 310 是主鍵索引,hidden = 0 的記錄是主鍵字段;hidden = 1 的記錄是主鍵索引中的其它字段,也就是表中的字段。
除了在 Debug 版本的 MySQL 中設(shè)置跳過(guò)數(shù)據(jù)字典表的權(quán)限檢查之外,還可以通過(guò) information_schema 數(shù)據(jù)庫(kù)中的表或視圖查看其對(duì)應(yīng)的數(shù)據(jù)字典表: 數(shù)據(jù)字典表information_schema 表或視圖tablesINNODB_TABLEScolumnsINNODB_COLUMNSindexesINNODB_INDEXESindex_column_usage 3. 數(shù)據(jù)字典表元數(shù)據(jù)在哪里?數(shù)據(jù)字典表用于存儲(chǔ)用戶表的元數(shù)據(jù),這個(gè)比較好理解,因?yàn)閯?chuàng)建用戶表的時(shí)候,所有數(shù)據(jù)字典表都已經(jīng)存在了,把用戶表的各種元數(shù)據(jù)插入到相應(yīng)的數(shù)據(jù)字典表就可以了。 數(shù)據(jù)字典表本身的元數(shù)據(jù)也會(huì)保存到數(shù)據(jù)字典表里,但是某個(gè)數(shù)據(jù)字典表創(chuàng)建的時(shí)候,有一些數(shù)據(jù)字典表還沒(méi)有創(chuàng)建,這就有問(wèn)題了。 我們以 columns、indexes 這 2 個(gè)數(shù)據(jù)字典表為例來(lái)說(shuō)明:columns 表先于 indexes 表創(chuàng)建,columns 表創(chuàng)建成功之后,需要把索引元數(shù)據(jù)保存到 indexes 表中,而此時(shí) indexes 表還沒(méi)有創(chuàng)建,columns 表的索引元數(shù)據(jù)自然也就沒(méi)辦法保存到 indexes 表中了。 MySQL 解決這個(gè)問(wèn)題的方案是引入一個(gè)中間層,用于臨時(shí)存放所有數(shù)據(jù)字典表的各種元數(shù)據(jù),等到所有數(shù)據(jù)字典表都創(chuàng)建完成之后,再把臨時(shí)存放在中間層的所有數(shù)據(jù)字典表的元數(shù)據(jù)保存到相應(yīng)的數(shù)據(jù)字典表中。 這里所謂的中間層實(shí)際上是一個(gè)存儲(chǔ)適配器,源碼中對(duì)應(yīng)的類名為 Storage_adapter,這是一個(gè)實(shí)現(xiàn)了單例模式的類。 MySQL 在初始化數(shù)據(jù)目錄的過(guò)程中,Storage_adapter 類的實(shí)例屬性 m_core_registry 就是所有數(shù)據(jù)字典表元數(shù)據(jù)的臨時(shí)存放場(chǎng)所。 4. 創(chuàng)建數(shù)據(jù)字典表我們安裝 MySQL 完成之后,想讓 MySQL 運(yùn)行起來(lái),要做的第一件事就是初始化 MySQL,實(shí)際上就是初始化 MySQL 數(shù)據(jù)目錄。 初始化過(guò)程會(huì)創(chuàng)建 MySQL 運(yùn)行時(shí)需要的各種表空間、數(shù)據(jù)庫(kù)、表,其中就包含數(shù)據(jù)字典表。 創(chuàng)建數(shù)據(jù)字典表的過(guò)程分為 3 個(gè)步驟進(jìn)行: 第 1 步,把代表每個(gè)數(shù)據(jù)字典表的 Object_table 對(duì)象注冊(cè)到 System_tables 類的實(shí)例屬性 m_registry 中。
第 2 步,循環(huán) m_registry 中的所有表,通過(guò) Object_table 得到數(shù)據(jù)字典表的 DDL,然后調(diào)用 dd::execute_query() 執(zhí)行 DDL 語(yǔ)句創(chuàng)建數(shù)據(jù)字典表。 dd::execute_query() 創(chuàng)建數(shù)據(jù)字典表的過(guò)程中,會(huì)把表的元數(shù)據(jù)臨時(shí)存放到 Storage_adapter 類的實(shí)例屬性 m_core_registry 中,而不會(huì)保存到各種元數(shù)據(jù)對(duì)應(yīng)的數(shù)據(jù)字典表中,這么做的原因在上一小節(jié)中介紹數(shù)據(jù)字典表的元數(shù)據(jù)在哪里時(shí),已經(jīng)介紹過(guò)了,這里不再贅述。 dd::execute_query() 執(zhí)行完一個(gè)數(shù)據(jù)字典表的 DDL 語(yǔ)句之后,這個(gè)數(shù)據(jù)字典表在表空間中就已經(jīng)存在了,m_registry 中的所有表都處理完成之后,所有數(shù)據(jù)字典表就都存在了。 ![]() 第 3 步,循環(huán) m_registry 中的所有表,把每個(gè)表本身的元數(shù)據(jù)(數(shù)據(jù)庫(kù) ID、表 ID、表名、注釋、字段數(shù)量等)保存到 mysql.tables 數(shù)據(jù)字典表中,然后把表的字段、索引等元數(shù)據(jù)保存到對(duì)應(yīng)的數(shù)據(jù)字典表中。
![]() 經(jīng)過(guò) 3 個(gè)步驟的通力協(xié)作,所有數(shù)據(jù)字典表的元數(shù)據(jù)就都保存到數(shù)據(jù)字典表中了,這個(gè)雞生蛋、蛋生雞的問(wèn)題,就這樣通過(guò)引入外力(m_core_registry)解決了。 5. 打開(kāi)數(shù)據(jù)字典表數(shù)據(jù)字典表保存著 MySQL 運(yùn)行過(guò)程中需要的一系列關(guān)鍵數(shù)據(jù),使用頻次很高,MySQL 啟動(dòng)過(guò)程中就會(huì)把數(shù)據(jù)字典表的元數(shù)據(jù)都加載到內(nèi)存中,這就是打開(kāi)表的過(guò)程。 也就是說(shuō),打開(kāi)數(shù)據(jù)字典表是在 MySQL 啟動(dòng)過(guò)程中完成的。 前面我們介紹過(guò),數(shù)據(jù)字典表的元數(shù)據(jù)也是保存在數(shù)據(jù)字典表中的。 MySQL 啟動(dòng)過(guò)程中,要先打開(kāi)數(shù)據(jù)字典表才能拿到數(shù)據(jù)字典表的元數(shù)據(jù),而要拿到數(shù)據(jù)字典表的元數(shù)據(jù),又必須先打開(kāi)數(shù)據(jù)字典表。 這個(gè)過(guò)程很繞,不是很好理解,我們來(lái)打個(gè)比方:數(shù)據(jù)字典表是一個(gè)房間,數(shù)據(jù)字典表的元數(shù)據(jù)是打開(kāi)房間門的鑰匙。 現(xiàn)在問(wèn)題來(lái)了,因?yàn)?MySQL 把數(shù)據(jù)字典表的元數(shù)據(jù)保存在數(shù)據(jù)字典表中,這就相當(dāng)于把打開(kāi)房間門的鑰匙落在房間里了。 要想打開(kāi)房間,必須先拿到鑰匙,而要想拿到鑰匙又必須先打開(kāi)房間,這樣一轉(zhuǎn)換,問(wèn)題是不是更好理解點(diǎn)了? 我們先來(lái)想想怎么解決房間和鑰匙問(wèn)題,如果把打開(kāi)房間的鑰匙落在房間里了,有哪些辦法可以解決? 我能想到的有以下 3 種解決方案:
MySQL 里沒(méi)有前 2 種方案,而是留了一把備用鑰匙,也就是第 3 種方案,接下來(lái)我們看看 MySQL 打開(kāi)數(shù)據(jù)字典表的過(guò)程: 第 1 步,和創(chuàng)建數(shù)據(jù)字典表一樣,把代表每個(gè)數(shù)據(jù)字典表的 Object_table 對(duì)象注冊(cè)到 System_tables 類的實(shí)例屬性 m_registry 中。 每個(gè)數(shù)據(jù)字典表的 Object_table 對(duì)象中,都定義了這個(gè)表的表名、字段、索引、外鍵等信息。 Object_table 對(duì)象中保存的并不是 DDL 語(yǔ)句,卻類似于我們建表時(shí)的 DDL 語(yǔ)句。 下面這個(gè)例子是源碼中表空間數(shù)據(jù)字典表 mysql.tablespaces Object_table 對(duì)象中定義的該表的信息: Tablespaces::Tablespaces() { // 表名 m_target_def.set_table_name('tablespaces'); // 字段 m_target_def.add_field(FIELD_ID, 'FIELD_ID', 'id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT'); m_target_def.add_field(FIELD_NAME, 'FIELD_NAME', 'name VARCHAR(268) NOT NULL COLLATE ' + String_type(Object_table_definition_impl::name_collation()->m_coll_name)); m_target_def.add_field(FIELD_OPTIONS, 'FIELD_OPTIONS', 'options MEDIUMTEXT'); m_target_def.add_field(FIELD_SE_PRIVATE_DATA, 'FIELD_SE_PRIVATE_DATA', 'se_private_data MEDIUMTEXT'); m_target_def.add_field(FIELD_COMMENT, 'FIELD_COMMENT', 'comment VARCHAR(2048) NOT NULL'); m_target_def.add_field(FIELD_ENGINE, 'FIELD_ENGINE', 'engine VARCHAR(64) NOT NULL COLLATE utf8_general_ci'); m_target_def.add_field(FIELD_ENGINE_ATTRIBUTE, 'FIELD_ENGINE_ATTRIBUTE', 'engine_attribute JSON'); // 索引 m_target_def.add_index(INDEX_PK_ID, 'INDEX_PK_ID', 'PRIMARY KEY(id)'); m_target_def.add_index(INDEX_UK_NAME, 'INDEX_UK_NAME', 'UNIQUE KEY(name)'); // 如果有外鍵等其它信息,也會(huì)加在這里}第 2 步,循環(huán) m_registry 中的所有表,通過(guò) Object_table 得到數(shù)據(jù)字典表的 DDL,然后調(diào)用 dd::execute_query() 執(zhí)行 DDL 語(yǔ)句創(chuàng)建數(shù)據(jù)字典表。 和創(chuàng)建數(shù)據(jù)字典表中的第 2 步不一樣,dd::execute_query() 執(zhí)行 DDL,并不會(huì)真正的創(chuàng)建表,只是為了生成數(shù)據(jù)字典表元數(shù)據(jù),并把元數(shù)據(jù)保存到 Storage_adapter 類的實(shí)例屬性 m_core_registry 中。 保存到 m_core_registry 中的數(shù)據(jù)字典表元數(shù)據(jù),就是我們前面說(shuō)的備用鑰匙,有了這把備用鑰匙,就能打開(kāi)數(shù)據(jù)字典表了。 ![]() 第 3 步,循環(huán) m_registry 中的所有表,通過(guò)第 2 步生成的數(shù)據(jù)字典表元數(shù)據(jù),去 mysql 表空間中(表空間文件:mysql.ibd)讀取各個(gè)數(shù)據(jù)字典表的元數(shù)據(jù)。 這一步執(zhí)行完成之后,所有數(shù)據(jù)字典表的元數(shù)據(jù)都被加載到內(nèi)存中了,數(shù)據(jù)字典表都被打開(kāi)了。 第 4 步,循環(huán) m_registry 中的所有表,把數(shù)據(jù)字典表的元數(shù)據(jù)從 m_core_registry 刪除。 第 5 步,循環(huán) m_registry 中所有的表,把從表空間中讀取出來(lái)的數(shù)據(jù)字典表的元數(shù)據(jù)存入 m_core_registry 中。 不過(guò),這一步存入 m_core_registry 的并不是所有數(shù)據(jù)字典表的元數(shù)據(jù),而是 22 個(gè)核心(CORE)數(shù)據(jù)字典表的元數(shù)據(jù): 1 ~ 5 步執(zhí)行完成之后,m_core_registry 中就只包含上面 22 個(gè)核心數(shù)據(jù)字典表的元數(shù)據(jù)了,有了這些表的元數(shù)據(jù),就可以打開(kāi)其它所有表了。 第 6 步,調(diào)用 dd::execute_query() 執(zhí)行 FLUSH TABLES 關(guān)閉已經(jīng)打開(kāi)的所有數(shù)據(jù)字典表、非數(shù)據(jù)字典表,后續(xù)就可以用從數(shù)據(jù)字典表中讀取出來(lái)的元數(shù)據(jù)來(lái)打開(kāi)數(shù)據(jù)字典表和其它所有需要的表了。 到這里,打開(kāi)數(shù)據(jù)字典表的大體流程就已經(jīng)介紹完了,也許大家會(huì)有疑問(wèn): 第 2 步調(diào)用 dd::execute_query() 執(zhí)行 DDL,已經(jīng)拿到了數(shù)據(jù)字典表的元數(shù)據(jù)。
第 3 步根據(jù)備用元數(shù)據(jù)打開(kāi)數(shù)據(jù)字典表,從表空間中讀取到數(shù)據(jù)字典表的元數(shù)據(jù)。
第 4 步從 m_core_registry 中刪除備用元數(shù)據(jù)。第 5 步把原配元數(shù)據(jù)存入 m_core_registry。 數(shù)據(jù)字典表的備用元數(shù)據(jù)和原配元數(shù)據(jù)不是一樣的嗎?為什么還要用原配元數(shù)據(jù)替換備用元數(shù)據(jù),這是不是多此一舉? 我沒(méi)有逐個(gè)對(duì)比備用元數(shù)據(jù)和原配元數(shù)據(jù)是否完全一樣,這是個(gè)不小的工程。不過(guò),既然源碼中這么實(shí)現(xiàn),那應(yīng)該是有它的原因,只是我還沒(méi)有發(fā)現(xiàn)。如果后面發(fā)現(xiàn)其中的原因,我會(huì)再補(bǔ)充到我的博客中。 6. 總結(jié)要理解 MySQL 8.0 中的數(shù)據(jù)字典表,核心是理解以下 2 點(diǎn):
|
|
|
來(lái)自: copy_left > 《mysql相關(guān)》