解讀MySQL 8.0數據字典的初始化與啟動

来源:https://www.cnblogs.com/huaweiyun/p/18264934
-Advertisement-
Play Games

本文分享自華為雲社區《MySQL全文索引源碼剖析之Insert語句執行過程》,作者:GaussDB 資料庫。 本文主要介紹MySQL 8.0數據字典的基本概念和數據字典的初始化與啟動載入的主要流程。 MySQL 8.0數據字典簡介 數據字典(Data Dictionary, DD)用來存儲資料庫內部 ...


本文分享自華為雲社區《MySQL全文索引源碼剖析之Insert語句執行過程》,作者:GaussDB 資料庫。

本文主要介紹MySQL 8.0數據字典的基本概念和數據字典的初始化與啟動載入的主要流程。

MySQL 8.0數據字典簡介

數據字典(Data Dictionary, DD)用來存儲資料庫內部對象的信息,這些信息也被稱為元數據(Metadata),包括schema名稱、表結構、存儲過程的定義等。

 

1.PNG

圖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.PNG

圖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.PNG

圖3 dd::tables::Schemata類

對於這些表中存儲的元數據所對應的對象,或者說這些表中的每一行數據所對應的一個對象,比如一個schema、table、column等,代碼中也有對應的類。

還是以schema為例,它對應的類是dd::Schema,實現類是dd::Schema_impl,代表的是schema這種資料庫內部對象,也是mysql.schemata表中的一行。

所有數據字典中所存儲的對象在代碼中的基類都是dd::Weak_object,如圖4:

4.PNG

圖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.PNG

圖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數據字典版本升級的內容,敬請期待。

更多技術專欄內容:https://bbs.huaweicloud.com/blogs/427078

 

點擊關註,第一時間瞭解華為雲新鮮技術~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在伺服器linux系統環境下,想要上傳和下載文件到本地PC通常是比較麻煩的, 在這個過程中,將層級複雜的文件夾壓縮成壓縮包再進行上傳/下載更為方便, 其中常用到的linux指令就是 zip / unzip, 文件壓縮指令 zip 個人認為,在日常科研中,常用的參數有兩個: -q 不顯示指令執行過程( ...
  • 0、思考與回答 0.1、思考一 為什麼要增加時間片輪詢? 目前的 RTOS 內核已經支持搶占優先順序,即高優先順序的任務會搶占低優先順序的任務得到執行,但是對於同等優先順序的任務,如果不支持時間片輪詢,則只能有一個任務運行,並且由於優先順序相同所以除延時阻塞到期外也不會發生任務調度,因此需要增加時間片輪詢保證 ...
  • 本文介紹在Linux Ubuntu操作系統的電腦中,安裝Anaconda環境與Python語言的方法。 在之前的文章Anaconda與Python環境在Windows中的部署中,我們介紹了在Win10電腦中,安裝Anaconda環境與Python語言的方法;而在本文中,我們就詳細介紹一下在Linux ...
  • 0、思考與回答 0.1、思考一 如何處理進入阻塞狀態的任務? 為了讓 RTOS 支持多優先順序,我們創建了多個就緒鏈表(數組形式),用每一個就緒鏈表表示一個優先順序,對於阻塞狀態的任務顯然要從就緒鏈表中移除,但是阻塞狀態的任務並不是永久阻塞了,等待一段時間後應該從阻塞狀態恢復,所以我們需要創建一個阻塞鏈 ...
  • liwen01 2024.06.16 前言 先提幾個問題:什麼是文件系統崩潰一致性?為什麼會出現文件系統崩潰一致性問題?有哪些方法可以解這個問題?它們各自又有哪些局限性? window系統電腦異常後會藍屏、手機死機卡頓後我們會手動給它重啟,大部分設備的系統在遇到不可修複的嚴重異常後都會嘗試通過重啟來 ...
  • 本文介紹瞭如何根據所使用的不同開發板配置不同的交叉編譯環境. 由於在移植LVGL到不同開發板上時遇到了一些問題, 故在問題解決後整理和總結和該文章. ...
  • 隨著技術的發展,用戶對軟體的界面美觀度和交互體驗的要求越來越高。在這樣的背景下,可視化開發UI(User Interface)成為了提升用戶體驗和開發效率的重要工具。 通過圖形界面來設計和構建用戶界面的方法,可視化開發UI可以說改變了軟便開發的生態,與傳統的代碼編寫相比,它允許開發者使用拖放等直觀的 ...
  • 本文分享自天翼雲開發者社區《TiDB體系架構》,作者:x****n 如圖所示,TiDB體系中三大組成部分:PD、TiDB Server、TiKV 1.PD:負責產生全局的TSO時間、控制Region在TIkv中的分佈、產生全局事務ID、還有其他ID。 2.TiDB:沒有數據落地,接收客戶端sql語句 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...