MySQL 深潛 - 一文詳解 MySQL Data Dictionary
一 背景
在 MySQL 8.0 之前,Server 層和存儲(chǔ)引擎(比如 InnoDB)會(huì)各自保留一份元數(shù)據(jù)(schema name, table definition 等),不僅在信息存儲(chǔ)上有著重復(fù)冗余,而且可能存在兩者之間存儲(chǔ)的元數(shù)據(jù)不同步的現(xiàn)象。不同存儲(chǔ)引擎之間(比如 InnoDB 和 MyISAM)有著不同的元數(shù)據(jù)存儲(chǔ)形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元數(shù)據(jù)無法統(tǒng)一管理。此外,將元數(shù)據(jù)存放在不支持事務(wù)的表和文件中,使得 DDL 變更不會(huì)是原子的,crash recovery 也會(huì)成為一個(gè)問題。
為了解決上述問題,MySQL 在 8.0 中引入了 data dictionary 來進(jìn)行 Server 層和不同引擎間統(tǒng)一的元數(shù)據(jù)管理,這些元數(shù)據(jù)都存儲(chǔ)在 InnoDB 引擎的表中,自然的支持原子性,且 Server 層和引擎層共享一份元數(shù)據(jù),不再存在不同步的問題。
二 整體架構(gòu)
典表的讀寫操作,包含開表(open table)、構(gòu)造主鍵、主鍵查找等過程。client 和底層存儲(chǔ)之間通過兩級(jí)緩存來加速對(duì)元數(shù)據(jù)對(duì)象的內(nèi)存訪問,兩級(jí)緩存都是基于 hash map 實(shí)現(xiàn)的,一層緩存是 local 的,由每個(gè) client(每個(gè)線程對(duì)應(yīng)一個(gè) client)獨(dú)享;二級(jí)緩存是 share 的,為所有線程共享的全局緩存。下面我將對(duì) data dictionary 的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)架構(gòu)做重點(diǎn)介紹,也會(huì)分享一個(gè)支持原子的 DDL 在 data dictionary 層面的實(shí)現(xiàn)過程。
三 metadata 在內(nèi)存和引擎層面的表示
data dictionary (簡稱DD)中的數(shù)據(jù)結(jié)構(gòu)是完全按照多態(tài)、接口/實(shí)現(xiàn)的形式來組織的,接口通過純虛類來實(shí)現(xiàn)(比如表示一個(gè)表的 Table),其實(shí)現(xiàn)類(Table_impl)為接口類的名字加 _impl 后綴。下面以 Table_impl 為例介紹一個(gè)表的元數(shù)據(jù)對(duì)象在 DD cache 中的表示。
1 Table_impl
Table_impl 類中包含一個(gè)表相關(guān)的元數(shù)據(jù)屬性定義,比如下列最基本引擎類型、comment、分區(qū)類型、分區(qū)表達(dá)式等。
- class Table_impl : public Abstract_table_impl, virtual public Table { // Fields. Object_id m_se_private_id; String_type m_engine; String_type m_comment; // - Partitioning related fields. enum_partition_type m_partition_type; String_type m_partition_expression; String_type m_partition_expression_utf8; enum_default_partitioning m_default_partitioning; // References to tightly-coupled objects. Index_collection m_indexes; Foreign_key_collection m_foreign_keys; Foreign_key_parent_collection m_foreign_key_parents; Partition_collection m_partitions; Partition_leaf_vector m_leaf_partitions; Trigger_collection m_triggers; Check_constraint_collection m_check_constraints;};
Table_impl 也是代碼實(shí)現(xiàn)中 client 最常訪問的內(nèi)存結(jié)構(gòu),開發(fā)者想要增加新的屬性,直接在這個(gè)類中添加和初始化即可,但是僅僅如此不會(huì)自動(dòng)將該屬性持久化到存儲(chǔ)引擎中。除了上述簡單屬性之外,還包括與一個(gè)表相關(guān)的復(fù)雜屬性,比如列信息、索引信息、分區(qū)信息等,這些復(fù)雜屬性都是存在其他的 DD 表中,在內(nèi)存 cache 中也都會(huì)集成到 Table_impl 對(duì)象里。
從 Abstract_table_impl 繼承來的 Collection m_columns 就表示表的所有列集合,集合中的每一個(gè)對(duì)象 Column_impl 表示該列的元信息,包括數(shù)值類型、是否為 NULL、是否自增、默認(rèn)值等。同時(shí)也包含指向 Abstract_table_impl 的指針,將該列與其對(duì)應(yīng)的表聯(lián)系起來。
- class Column_impl : public Entity_object_impl, public Column { // Fields. enum_column_types m_type; bool m_is_nullable; bool m_is_zerofill; bool m_is_unsigned; bool m_is_auto_increment; bool m_is_virtual; bool m_default_value_null; String_type m_default_value; // References to tightly-coupled objects. Abstract_table_impl *m_table;};
此外 Table_impl 中也包含所有分區(qū)的元信息集合 Collection m_partitions,存放每個(gè)分區(qū)的 id、引擎、選項(xiàng)、范圍值、父子分區(qū)等。
- class Partition_impl : public Entity_object_impl, public Partition { // Fields. Object_id m_parent_partition_id; uint m_number; Object_id m_se_private_id; String_type m_description_utf8; String_type m_engine; String_type m_comment; Properties_impl m_options; Properties_impl m_se_private_data; // References to tightly-coupled objects. Table_impl *m_table; const Partition *m_parent; Partition_values m_values; Partition_indexes m_indexes; Table::Partition_collection m_sub_partitions;};
因此獲取到一個(gè)表的 Table_impl,我們就可以獲取到與這個(gè)表相關(guān)聯(lián)的所有元信息。
2 Table_impl 是如何持久化存儲(chǔ)和訪問的
DD cache 中的元信息都是在 DD tables 中讀取和存儲(chǔ)的,每個(gè)表存放一類元信息的基本屬性字段,比如 tables、columns、indexes等,他們之間通過主外鍵關(guān)聯(lián)連接起來,組成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空間中,在 release 版本對(duì)用戶隱藏,只能通過 INFORMATION SCHEMA 的部分視圖查看;在 debug 版本可通過設(shè)置 SET debug='+d,skip_dd_table_access_check' 直接訪問查看。比如:
- root@localhost:test 8.0.18-debug> SHOW CREATE TABLE mysql.tables\G*************************< strong> 1. row < /strong>************************* Table: tablesCreate Table: CREATE TABLE `tables` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `schema_id` bigint(20) unsigned NOT NULL, `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, `type` enum('BASE TABLE','VIEW','SYSTEM VIEW') COLLATE utf8_bin NOT NULL, `engine` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `mysql_version_id` int(10) unsigned NOT NULL, `row_format` enum('Fixed','Dynamic','Compressed','Redundant','Compact','Paged') COLLATE utf8_bin DEFAULT NULL, `collation_id` bigint(20) unsigned DEFAULT NULL, `comment` varchar(2048) COLLATE utf8_bin NOT NULL, `hidden` enum('Visible','System','SE','DDL') COLLATE utf8_bin NOT NULL, `options` mediumtext COLLATE utf8_bin, `se_private_data` mediumtext COLLATE utf8_bin, `se_private_id` bigint(20) unsigned DEFAULT NULL, `tablespace_id` bigint(20) unsigned DEFAULT NULL, `partition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55','RANGE','LIST','RANGE_COLUMNS','LIST_COLUMNS','AUTO','AUTO_LINEAR') COLLATE utf8_bin DEFAULT NULL, `partition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL, `partition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL, `default_partitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL, `subpartition_type` enum('HASH','KEY_51','KEY_55','LINEAR_HASH','LINEAR_KEY_51','LINEAR_KEY_55') COLLATE utf8_bin DEFAULT NULL, `subpartition_expression` varchar(2048) COLLATE utf8_bin DEFAULT NULL, `subpartition_expression_utf8` varchar(2048) COLLATE utf8_bin DEFAULT NULL, `default_subpartitioning` enum('NO','YES','NUMBER') COLLATE utf8_bin DEFAULT NULL, `created` timestamp NOT NULL, `last_altered` timestamp NOT NULL, `view_definition` longblob, `view_definition_utf8` longtext COLLATE utf8_bin, `view_check_option` enum('NONE','LOCAL','CASCADED') COLLATE utf8_bin DEFAULT NULL, `view_is_updatable` enum('NO','YES') COLLATE utf8_bin DEFAULT NULL, `view_algorithm` enum('UNDEFINED','TEMPTABLE','MERGE') COLLATE utf8_bin DEFAULT NULL, `view_security_type` enum('DEFAULT','INVOKER','DEFINER') COLLATE utf8_bin DEFAULT NULL, `view_definer` varchar(288) COLLATE utf8_bin DEFAULT NULL, `view_client_collation_id` bigint(20) unsigned DEFAULT NULL, `view_connection_collation_id` bigint(20) unsigned DEFAULT NULL, `view_column_names` longtext COLLATE utf8_bin, `last_checked_for_upgrade_version_id` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `schema_id` (`schema_id`,`name`), UNIQUE KEY `engine` (`engine`,`se_private_id`), KEY `engine_2` (`engine`), KEY `collation_id` (`collation_id`), KEY `tablespace_id` (`tablespace_id`), KEY `type` (`type`), KEY `view_client_collation_id` (`view_client_collation_id`), KEY `view_connection_collation_id` (`view_connection_collation_id`), CONSTRAINT `tables_ibfk_1` FOREIGN KEY (`schema_id`) REFERENCES `schemata` (`id`), CONSTRAINT `tables_ibfk_2` FOREIGN KEY (`collation_id`) REFERENCES `collations` (`id`), CONSTRAINT `tables_ibfk_3` FOREIGN KEY (`tablespace_id`) REFERENCES `tablespaces` (`id`), CONSTRAINT `tables_ibfk_4` FOREIGN KEY (`view_client_collation_id`) REFERENCES `collations` (`id`), CONSTRAINT `tables_ibfk_5` FOREIGN KEY (`view_connection_collation_id`) REFERENCES `collations` (`id`)) /*!50100 TABLESPACE `mysql` */ ENGINE=InnoDB AUTO_INCREMENT=549 DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC1 row in set (0.00 sec)
通過以上 mysql.tables 的表定義可以獲得存儲(chǔ)引擎中實(shí)際存儲(chǔ)的元信息字段。DD tables 包括 tables、schemata、columns、column_type_elements、indexes、index_column_usage、foreign_keys、foreign_key_column_usage、table_partitions、table_partition_values、index_partitions、triggers、check_constraints、view_table_usage、view_routine_usage 等。
Storage_adapter 是訪問持久存儲(chǔ)引擎的處理類,包括 get() / drop() / store() 等接口。當(dāng)初次獲取一個(gè)表的元信息時(shí),會(huì)調(diào)用 Storage_adapter::get() 接口,處理過程如下:
- Storage_adapter::get() // 根據(jù)訪問對(duì)象類型,將依賴的 DD tables 加入到 open table list 中 |--Open_dictionary_tables_ctx::register_tables< T>() |--Table_impl::register_tables() |--Open_dictionary_tables_ctx::open_tables() // 調(diào)用 Server 層接口打開所有表 |--Raw_table::find_record() // 直接調(diào)用 handler 接口根據(jù)傳入的 key(比如表名)查找記錄 |--handler::ha_index_read_idx_map() // index read // 從讀取到的 record 中解析出對(duì)應(yīng)屬性,調(diào)用 field[field_no]->val_xx() 函數(shù) |--Table_impl::restore_attributes() // 通過調(diào)用 restore_children() 函數(shù)從與該對(duì)象關(guān)聯(lián)的其他 DD 表中根據(jù)主外鍵讀取完整的元數(shù)據(jù)定義 |--Table_impl::restore_children() |--返回完整的 DD cache 對(duì)象
上述在獲取列和屬性的對(duì)應(yīng)關(guān)系時(shí),根據(jù)的是 Tables 對(duì)象的枚舉類型下標(biāo),按順序包含了該類型 DD 表中的所有列,與上述表定義是一一對(duì)應(yīng)的。因此如果我們需要新增 DD 表中存儲(chǔ)的列時(shí),也需要往下面枚舉類型定義中加入對(duì)應(yīng)的列,并且在 Table_impl::restore_attributes() / Table_impl::store_attributes() 函數(shù)中添加對(duì)新增列的讀取和存儲(chǔ)操作。
- class Tables : public Entity_object_table_impl { enum enum_fields { FIELD_ID, FIELD_SCHEMA_ID, FIELD_NAME, FIELD_TYPE, FIELD_ENGINE, FIELD_MYSQL_VERSION_ID, FIELD_ROW_FORMAT, FIELD_COLLATION_ID, FIELD_COMMENT, FIELD_HIDDEN, FIELD_OPTIONS, FIELD_SE_PRIVATE_DATA, FIELD_SE_PRIVATE_ID, FIELD_TABLESPACE_ID, FIELD_PARTITION_TYPE, FIELD_PARTITION_EXPRESSION, FIELD_PARTITION_EXPRESSION_UTF8, FIELD_DEFAULT_PARTITIONING, FIELD_SUBPARTITION_TYPE, FIELD_SUBPARTITION_EXPRESSION, FIELD_SUBPARTITION_EXPRESSION_UTF8, FIELD_DEFAULT_SUBPARTITIONING, FIELD_CREATED, FIELD_LAST_ALTERED, FIELD_VIEW_DEFINITION, FIELD_VIEW_DEFINITION_UTF8, FIELD_VIEW_CHECK_OPTION, FIELD_VIEW_IS_UPDATABLE, FIELD_VIEW_ALGORITHM, FIELD_VIEW_SECURITY_TYPE, FIELD_VIEW_DEFINER, FIELD_VIEW_CLIENT_COLLATION_ID, FIELD_VIEW_CONNECTION_COLLATION_ID, FIELD_VIEW_COLUMN_NAMES, FIELD_LAST_CHECKED_FOR_UPGRADE_VERSION_ID, NUMBER_OF_FIELDS // Always keep this entry at the end of the enum };};
四 多級(jí)緩存
為了避免每次對(duì)元數(shù)據(jù)對(duì)象的訪問都需要去持久存儲(chǔ)中讀取多個(gè)表的數(shù)據(jù),使生成的元數(shù)據(jù)內(nèi)存對(duì)象能夠復(fù)用,data dictionary 實(shí)現(xiàn)了兩級(jí)緩存的架構(gòu),第一級(jí)是 client local 獨(dú)享的,核心數(shù)據(jù)結(jié)構(gòu)為 Local_multi_map,用于加速在當(dāng)前線程中對(duì)于相同對(duì)象的重復(fù)訪問,同時(shí)在當(dāng)前線程涉及對(duì) DD 對(duì)象的修改(DDL)時(shí)管理 committed、uncommitted、dropped 幾種狀態(tài)的對(duì)象。第二級(jí)就是比較常見的多線程共享的緩存,核心數(shù)據(jù)結(jié)構(gòu)為 Shared_multi_map,包含著所有線程都可以訪問到其中的對(duì)象,所以會(huì)做并發(fā)控制的處理。
兩級(jí)緩存的底層實(shí)現(xiàn)很統(tǒng)一,都是基于 hash map 的,目前的實(shí)現(xiàn)是 std::map。Local_multi_map 和 Shared_multi_map都是派生于 Multi_map_base。
之所以叫 Multi_map_base,是因?yàn)槠渲邪硕鄠€(gè) hash map,適合用戶根據(jù)不同類型的 key 來獲取緩存對(duì)象,比如 id、name、DD cache 本身等。Element_map 就是對(duì) std::map 的一個(gè)封裝,key 為前述幾種類型之一,value 為 DD cache 對(duì)象指針的一個(gè)封裝 Cache_element,封裝了對(duì)象本身和引用計(jì)數(shù)。
Multi_map_base 對(duì)象實(shí)現(xiàn)了豐富的 m_map() 模板函數(shù),可以很方便的根據(jù) key 的類型不同選擇到對(duì)應(yīng)的 hash map。
Shared_multi_map 與 Local_multi_map 的不同在于,Shared_multi_map 還引入了一組 latch 與 condition variable 用于并發(fā)訪問中的線程同步與 cache miss 的處理。同時(shí)對(duì) Cache_element 對(duì)象做了內(nèi)存管理和復(fù)用的相關(guān)能力。
1 局部緩存
一級(jí)緩存位于每個(gè) Dictionary_client (每個(gè) client 與線程 THD 一一對(duì)應(yīng))內(nèi)部,由不同狀態(tài)(committed、uncommitted、dropped)的 Object_registry 組成。每個(gè) Object_registry 由不同元數(shù)據(jù)類型的 Local_multi_map 組成,用于管理不同類型的對(duì)象(比如表、schema、字符集、統(tǒng)計(jì)數(shù)據(jù)、Event 等)緩存。
其中 committed 狀態(tài)的 registry 就是我們訪問數(shù)據(jù)庫中已經(jīng)存在的對(duì)象時(shí),將其 DD cache object 存放在局部緩存中的位置。uncommitted 和 dropped 狀態(tài)的存在,主要用于當(dāng)前連接執(zhí)行的是一條 DDL 語句,在執(zhí)行過程中會(huì)將要 drop 的舊表對(duì)應(yīng)的 DD object 存放在 dropped 的 registry 中,將還未提交的新表定義對(duì)應(yīng)的 DD object 存放在 uncommitted 的 registry 中,用于執(zhí)行狀態(tài)的區(qū)分。
2 共享緩存
共享緩存是 Server 全局唯一的,使用單例 Shared_dictionary_cache 來實(shí)現(xiàn)。與上述局部緩存中 Object_registry 相似,Shared_dictionary_cache 也需要包含針對(duì)各種類型對(duì)象的緩存。與 Multi_map_base 實(shí)現(xiàn)根據(jù) key 類型自動(dòng)選取對(duì)應(yīng) hash map 的模版函數(shù)相似,Object_registry 和 Shared_dictionary_cache 也都實(shí)現(xiàn)了根據(jù)訪問對(duì)象的類型選擇對(duì)應(yīng)緩存的 m_map() 函數(shù),能夠很大程度上簡化函數(shù)調(diào)用。
與局部緩存可以無鎖訪問 hash map 不同,共享緩存在獲取 / 釋放 DD cache object 時(shí)都需要加鎖來完成引用計(jì)數(shù)的調(diào)整和防止訪問過程中被 destroy 掉。
3 緩存獲取過程
用戶通過 client 調(diào)用元數(shù)據(jù)對(duì)象獲取函數(shù),傳入元數(shù)據(jù)的 name 字符串,然后構(gòu)建出對(duì)應(yīng)的 name key,通過 key 去緩存中獲取元數(shù)據(jù)對(duì)象。獲取的整體過程就是一級(jí)局部緩存 -> 二級(jí)共享緩存 -> 存儲(chǔ)引擎。
- // Get a dictionary object.template < typename K, typename T>bool Dictionary_client::acquire(const K &key, const T **object, bool *local_committed, bool *local_uncommitted) { // Lookup in registry of uncommitted objects T *uncommitted_object = nullptr; bool dropped = false; acquire_uncommitted(key, &uncommitted_object, &dropped); ... // Lookup in the registry of committed objects. Cache_element< T> *element = NULL; m_registry_committed.get(key, &element); ... // Get the object from the shared cache. if (Shared_dictionary_cache::instance()->get(m_thd, key, &element)) { DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed || m_thd->is_error()); return true; }}
在一級(jí)局部緩存中獲取時(shí),會(huì)優(yōu)先去 uncommitted 和 dropped 的 registry 獲取,因?yàn)檫@兩者是最新的修改,同時(shí)判斷獲取對(duì)象是否已經(jīng)被 dropped。之后再會(huì)去 committed 的 registry 獲取,如果獲取到就直接返回,反之則去二級(jí)共享緩存中嘗試獲取。
Cache miss
共享緩存的獲取過程在 Shared_multi_map::get() 中實(shí)現(xiàn)。就是加鎖后直接的 hash map 查找,如果存在則給引用計(jì)數(shù)遞增后返回;如果不存在,就會(huì)進(jìn)入到 cache miss 的處理過程,調(diào)用上面介紹的存儲(chǔ)引擎的接口 Storage_adapter::get() 從 DD tables 中讀取,創(chuàng)建出來后依次加入共享緩存和局部緩存 committed registry 中。
由于開表訪問 DD tables,構(gòu)建 DD cache object 的過程相對(duì)耗時(shí),不會(huì)一直給 Shared_multi_map 加鎖,因此需要對(duì)并發(fā)訪問的 client 做并發(fā)控制。DD 的實(shí)現(xiàn)方法是第一個(gè)訪問的 client 會(huì)將 cache miss 的 key 加入到 Shared_multi_map的 m_missed 集合中,這個(gè)集合包含著現(xiàn)在所有正在讀取元數(shù)據(jù)的對(duì)象 key 值。之后訪問的 client 看到目標(biāo) key 值在 m_missed 集合中就會(huì)進(jìn)入等待。
當(dāng)?shù)谝粋€(gè) client 獲取到完整的 DD cache object,加入到共享緩存之后,移除 m_missed 集合中對(duì)應(yīng)的 key,并通過廣播的方式通知之前等待的線程重新在共享緩存中獲取。
五 Auto_releaser
Auto_releaser 是一個(gè) RAII 類,基本上在使用 client 訪問 DD cache 前都會(huì)做一個(gè)封裝,保證在整個(gè) Auto_releaser 對(duì)象存在的作用域內(nèi),所獲取到的 DD cache 對(duì)象都會(huì)在局部緩存中存在不釋放。Auto_releaser 包含需要 release 的對(duì)象 registry,通過 auto_release() 函數(shù)收集著當(dāng)前 client 從共享緩存中獲取到的 DD cache 對(duì)象,在超出其作用域進(jìn)行析構(gòu)時(shí)自動(dòng) release 對(duì)象,從局部緩存 committed 的 registry 中移除對(duì)象,并且在共享緩存中的引用計(jì)數(shù)遞減。
在嵌套函數(shù)調(diào)用過程中,可能在每一層都會(huì)有自己的 Auto_releaser,他們之間通過一個(gè)簡單的鏈表指針連接起來。在函數(shù)返回時(shí)將本層需要 release 的對(duì)象 release 掉,需要返回給上層使用的 DD cache 對(duì)象交給上層的 Auto_releaser 來負(fù)責(zé)。通過 transfer_release() 可以在不同層次的 Auto_releaser 對(duì)象間轉(zhuǎn)移需要 release 的對(duì)象,可以靈活的指定不再需要 DD cache 對(duì)象的層次。
六 應(yīng)用舉例:inplace DDL 過程中對(duì) DD 的操作
在 MySQL inplace DDL 執(zhí)行過程中,會(huì)獲取當(dāng)前表定義的 DD cache 對(duì)象,然后根據(jù)實(shí)際的 DDL 操作內(nèi)容構(gòu)造出新對(duì)應(yīng)的 DD 對(duì)象。然后依次調(diào)用 client 的接口完成對(duì)當(dāng)前表定義的刪除和新表定義的存儲(chǔ)。
- { if (thd->dd_client()->drop(table_def)) goto cleanup2; table_def = nullptr; DEBUG_SYNC_C("alter_table_after_dd_client_drop"); // Reset check constraint's mode. reset_check_constraints_alter_mode(altered_table_def); if ((db_type->flags & HTON_SUPPORTS_ATOMIC_DDL)) { /* For engines supporting atomic DDL we have delayed storing new table definition in the data-dictionary so far in order to avoid conflicts between old and new definitions on foreign key names. Since the old table definition is gone we can safely store new definition now. */ if (thd->dd_client()->store(altered_table_def)) goto cleanup2; }}.../* If the SE failed to commit the transaction, we must rollback the modified dictionary objects to make sure the DD cache, the DD tables and the state in the SE stay in sync.*/if (res) thd->dd_client()->rollback_modified_objects();else thd->dd_client()->commit_modified_objects();
在 drop() 過程中,會(huì)將當(dāng)前表定義的 DD cache 對(duì)象對(duì)應(yīng)的數(shù)據(jù)從存儲(chǔ)引擎中刪除,然后從共享緩存中移除(這要求當(dāng)前對(duì)象的引用計(jì)數(shù)僅為1,即只有當(dāng)前線程使用),之后加入到 dropped 局部緩存中。
在 store() 過程中,會(huì)將新的表定義寫入存儲(chǔ)引擎,并且將對(duì)應(yīng)的 DD cache 對(duì)象加入 uncommitted 緩存中。
在事務(wù)提交或者回滾后,client 將局部緩存中的 dropped 和 uncommitted registry 清除。由于 InnoDB 引擎支持事務(wù),持久存儲(chǔ)層面的數(shù)據(jù)會(huì)通過存儲(chǔ)引擎的接口提交或回滾,不需要 client 額外操作。
在這個(gè)過程中,由于 MDL(metadata lock) 的存在,不會(huì)有其他的線程嘗試訪問正在變更對(duì)象的 DD object,所以可以安全的對(duì) Shared_dictionary_cache 進(jìn)行操作。當(dāng) DDL 操作結(jié)束(提交或回滾),釋放 EXCLUSIVE 鎖之后,新的線程就可以重新從存儲(chǔ)引擎上加載新的表定義。
七 總結(jié)
MySQL data dictionary 解決了背景所述舊架構(gòu)中的諸多問題,使元數(shù)據(jù)的訪問更加安全,存儲(chǔ)和管理成本更低。架構(gòu)實(shí)現(xiàn)非常的精巧,通過大量的模版類實(shí)現(xiàn)使得代碼能夠最大程度上被復(fù)用。多層緩存的實(shí)現(xiàn)也能顯著提升訪問效率。通過 client 簡潔的接口,讓 Server 層和存儲(chǔ)層能在任何地方方便的訪問元數(shù)據(jù)。
參考
[1] MySQL8.0DataDictionary:BackgroundandMotivation
http://mysqlserverteam.com/mysql-8-0-data-dictionary-background-and-motivation/
[2] MySQL 8.0: Data Dictionary Architecture and Design
http://mysqlserverteam.com/mysql-8-0-data-dictionary-architecture-and-design/
[3] Source code mysql / mysql-server 8.0.18
https://github.com/mysql/mysql-server/tree/mysql-8.0.18