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ù)無(wú)法統(tǒng)一管理。此外,將元數(shù)據(jù)存放在不支持事務(wù)的表和文件中,使得 DDL 變更不會(huì)是原子的,crash recovery 也會(huì)成為一個(gè)問(wèn)題。
為了解決上述問(wèn)題,MySQL 在 8.0 中引入了 data dictionary 來(lái)進(jìn)行 Server 層和不同引擎間統(tǒng)一的元數(shù)據(jù)管理,這些元數(shù)據(jù)都存儲(chǔ)在 InnoDB 引擎的表中,自然的支持原子性,且 Server 層和引擎層共享一份元數(shù)據(jù),不再存在不同步的問(wèn)題。
二 整體架構(gòu)
典表的讀寫(xiě)操作,包含開(kāi)表(open table)、構(gòu)造主鍵、主鍵查找等過(guò)程。client 和底層存儲(chǔ)之間通過(guò)兩級(jí)緩存來(lái)加速對(duì)元數(shù)據(jù)對(duì)象的內(nèi)存訪(fǎng)問(wèn),兩級(jí)緩存都是基于 hash map 實(shí)現(xiàn)的,一層緩存是 local 的,由每個(gè) client(每個(gè)線(xiàn)程對(duì)應(yīng)一個(gè) client)獨(dú)享;二級(jí)緩存是 share 的,為所有線(xiàn)程共享的全局緩存。下面我將對(duì) data dictionary 的數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)架構(gòu)做重點(diǎn)介紹,也會(huì)分享一個(gè)支持原子的 DDL 在 data dictionary 層面的實(shí)現(xiàn)過(guò)程。
三 metadata 在內(nèi)存和引擎層面的表示
data dictionary (簡(jiǎn)稱(chēng)DD)中的數(shù)據(jù)結(jié)構(gòu)是完全按照多態(tài)、接口/實(shí)現(xiàn)的形式來(lái)組織的,接口通過(guò)純虛類(lèi)來(lái)實(shí)現(xiàn)(比如表示一個(gè)表的 Table),其實(shí)現(xiàn)類(lèi)(Table_impl)為接口類(lèi)的名字加 _impl 后綴。下面以 Table_impl 為例介紹一個(gè)表的元數(shù)據(jù)對(duì)象在 DD cache 中的表示。
1 Table_impl
Table_impl 類(lèi)中包含一個(gè)表相關(guān)的元數(shù)據(jù)屬性定義,比如下列最基本引擎類(lèi)型、comment、分區(qū)類(lèi)型、分區(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 最常訪(fǎng)問(wèn)的內(nèi)存結(jié)構(gòu),開(kāi)發(fā)者想要增加新的屬性,直接在這個(gè)類(lèi)中添加和初始化即可,但是僅僅如此不會(huì)自動(dòng)將該屬性持久化到存儲(chǔ)引擎中。除了上述簡(jiǎn)單屬性之外,還包括與一個(gè)表相關(guān)的復(fù)雜屬性,比如列信息、索引信息、分區(qū)信息等,這些復(fù)雜屬性都是存在其他的 DD 表中,在內(nèi)存 cache 中也都會(huì)集成到 Table_impl 對(duì)象里。
從 Abstract_table_impl 繼承來(lái)的 Collection m_columns 就表示表的所有列集合,集合中的每一個(gè)對(duì)象 Column_impl 表示該列的元信息,包括數(shù)值類(lèi)型、是否為 NULL、是否自增、默認(rèn)值等。同時(shí)也包含指向 Abstract_table_impl 的指針,將該列與其對(duì)應(yīng)的表聯(lián)系起來(lái)。
- 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ǔ)和訪(fǎng)問(wèn)的
DD cache 中的元信息都是在 DD tables 中讀取和存儲(chǔ)的,每個(gè)表存放一類(lèi)元信息的基本屬性字段,比如 tables、columns、indexes等,他們之間通過(guò)主外鍵關(guān)聯(lián)連接起來(lái),組成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空間中,在 release 版本對(duì)用戶(hù)隱藏,只能通過(guò) INFORMATION SCHEMA 的部分視圖查看;在 debug 版本可通過(guò)設(shè)置 SET debug='+d,skip_dd_table_access_check' 直接訪(fǎng)問(wèn)查看。比如:
- 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)
通過(guò)以上 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 是訪(fǎng)問(wèn)持久存儲(chǔ)引擎的處理類(lèi),包括 get() / drop() / store() 等接口。當(dāng)初次獲取一個(gè)表的元信息時(shí),會(huì)調(diào)用 Storage_adapter::get() 接口,處理過(guò)程如下:
- Storage_adapter::get() // 根據(jù)訪(fǎng)問(wèn)對(duì)象類(lèi)型,將依賴(lài)的 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 層接口打開(kāi)所有表 |--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() // 通過(guò)調(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ì)象的枚舉類(lèi)型下標(biāo),按順序包含了該類(lèi)型 DD 表中的所有列,與上述表定義是一一對(duì)應(yīng)的。因此如果我們需要新增 DD 表中存儲(chǔ)的列時(shí),也需要往下面枚舉類(lèi)型定義中加入對(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ì)象的訪(fǎng)問(wèn)都需要去持久存儲(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)前線(xiàn)程中對(duì)于相同對(duì)象的重復(fù)訪(fǎng)問(wèn),同時(shí)在當(dāng)前線(xiàn)程涉及對(duì) DD 對(duì)象的修改(DDL)時(shí)管理 committed、uncommitted、dropped 幾種狀態(tài)的對(duì)象。第二級(jí)就是比較常見(jiàn)的多線(xiàn)程共享的緩存,核心數(shù)據(jù)結(jié)構(gòu)為 Shared_multi_map,包含著所有線(xiàn)程都可以訪(fǎng)問(wèn)到其中的對(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,適合用戶(hù)根據(jù)不同類(lèi)型的 key 來(lái)獲取緩存對(duì)象,比如 id、name、DD cache 本身等。Element_map 就是對(duì) std::map 的一個(gè)封裝,key 為前述幾種類(lèi)型之一,value 為 DD cache 對(duì)象指針的一個(gè)封裝 Cache_element,封裝了對(duì)象本身和引用計(jì)數(shù)。
Multi_map_base 對(duì)象實(shí)現(xiàn)了豐富的 m_map() 模板函數(shù),可以很方便的根據(jù) key 的類(lèi)型不同選擇到對(duì)應(yīng)的 hash map。
Shared_multi_map 與 Local_multi_map 的不同在于,Shared_multi_map 還引入了一組 latch 與 condition variable 用于并發(fā)訪(fǎng)問(wèn)中的線(xiàn)程同步與 cache miss 的處理。同時(shí)對(duì) Cache_element 對(duì)象做了內(nèi)存管理和復(fù)用的相關(guān)能力。
1 局部緩存
一級(jí)緩存位于每個(gè) Dictionary_client (每個(gè) client 與線(xiàn)程 THD 一一對(duì)應(yīng))內(nèi)部,由不同狀態(tài)(committed、uncommitted、dropped)的 Object_registry 組成。每個(gè) Object_registry 由不同元數(shù)據(jù)類(lèi)型的 Local_multi_map 組成,用于管理不同類(lèi)型的對(duì)象(比如表、schema、字符集、統(tǒng)計(jì)數(shù)據(jù)、Event 等)緩存。
其中 committed 狀態(tài)的 registry 就是我們?cè)L問(wèn)數(shù)據(jù)庫(kù)中已經(jīng)存在的對(duì)象時(shí),將其 DD cache object 存放在局部緩存中的位置。uncommitted 和 dropped 狀態(tài)的存在,主要用于當(dāng)前連接執(zhí)行的是一條 DDL 語(yǔ)句,在執(zhí)行過(guò)程中會(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 來(lái)實(shí)現(xiàn)。與上述局部緩存中 Object_registry 相似,Shared_dictionary_cache 也需要包含針對(duì)各種類(lèi)型對(duì)象的緩存。與 Multi_map_base 實(shí)現(xiàn)根據(jù) key 類(lèi)型自動(dòng)選取對(duì)應(yīng) hash map 的模版函數(shù)相似,Object_registry 和 Shared_dictionary_cache 也都實(shí)現(xiàn)了根據(jù)訪(fǎng)問(wèn)對(duì)象的類(lèi)型選擇對(duì)應(yīng)緩存的 m_map() 函數(shù),能夠很大程度上簡(jiǎn)化函數(shù)調(diào)用。
與局部緩存可以無(wú)鎖訪(fǎng)問(wèn) hash map 不同,共享緩存在獲取 / 釋放 DD cache object 時(shí)都需要加鎖來(lái)完成引用計(jì)數(shù)的調(diào)整和防止訪(fǎng)問(wèn)過(guò)程中被 destroy 掉。
3 緩存獲取過(guò)程
用戶(hù)通過(guò) client 調(diào)用元數(shù)據(jù)對(duì)象獲取函數(shù),傳入元數(shù)據(jù)的 name 字符串,然后構(gòu)建出對(duì)應(yīng)的 name key,通過(guò) key 去緩存中獲取元數(shù)據(jù)對(duì)象。獲取的整體過(guò)程就是一級(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
共享緩存的獲取過(guò)程在 Shared_multi_map::get() 中實(shí)現(xiàn)。就是加鎖后直接的 hash map 查找,如果存在則給引用計(jì)數(shù)遞增后返回;如果不存在,就會(huì)進(jìn)入到 cache miss 的處理過(guò)程,調(diào)用上面介紹的存儲(chǔ)引擎的接口 Storage_adapter::get() 從 DD tables 中讀取,創(chuàng)建出來(lái)后依次加入共享緩存和局部緩存 committed registry 中。
由于開(kāi)表訪(fǎng)問(wèn) DD tables,構(gòu)建 DD cache object 的過(guò)程相對(duì)耗時(shí),不會(huì)一直給 Shared_multi_map 加鎖,因此需要對(duì)并發(fā)訪(fǎng)問(wèn)的 client 做并發(fā)控制。DD 的實(shí)現(xiàn)方法是第一個(gè)訪(fǎng)問(wèn)的 client 會(huì)將 cache miss 的 key 加入到 Shared_multi_map的 m_missed 集合中,這個(gè)集合包含著現(xiàn)在所有正在讀取元數(shù)據(jù)的對(duì)象 key 值。之后訪(fǎng)問(wèn)的 client 看到目標(biāo) key 值在 m_missed 集合中就會(huì)進(jìn)入等待。
當(dāng)?shù)谝粋€(gè) client 獲取到完整的 DD cache object,加入到共享緩存之后,移除 m_missed 集合中對(duì)應(yīng)的 key,并通過(guò)廣播的方式通知之前等待的線(xiàn)程重新在共享緩存中獲取。
五 Auto_releaser
Auto_releaser 是一個(gè) RAII 類(lèi),基本上在使用 client 訪(fǎng)問(wèn) DD cache 前都會(huì)做一個(gè)封裝,保證在整個(gè) Auto_releaser 對(duì)象存在的作用域內(nèi),所獲取到的 DD cache 對(duì)象都會(huì)在局部緩存中存在不釋放。Auto_releaser 包含需要 release 的對(duì)象 registry,通過(guò) 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)用過(guò)程中,可能在每一層都會(huì)有自己的 Auto_releaser,他們之間通過(guò)一個(gè)簡(jiǎn)單的鏈表指針連接起來(lái)。在函數(shù)返回時(shí)將本層需要 release 的對(duì)象 release 掉,需要返回給上層使用的 DD cache 對(duì)象交給上層的 Auto_releaser 來(lái)負(fù)責(zé)。通過(guò) transfer_release() 可以在不同層次的 Auto_releaser 對(duì)象間轉(zhuǎn)移需要 release 的對(duì)象,可以靈活的指定不再需要 DD cache 對(duì)象的層次。
六 應(yīng)用舉例:inplace DDL 過(guò)程中對(duì) DD 的操作
在 MySQL inplace DDL 執(zhí)行過(guò)程中,會(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() 過(guò)程中,會(huì)將當(dāng)前表定義的 DD cache 對(duì)象對(duì)應(yīng)的數(shù)據(jù)從存儲(chǔ)引擎中刪除,然后從共享緩存中移除(這要求當(dāng)前對(duì)象的引用計(jì)數(shù)僅為1,即只有當(dāng)前線(xiàn)程使用),之后加入到 dropped 局部緩存中。
在 store() 過(guò)程中,會(huì)將新的表定義寫(xiě)入存儲(chǔ)引擎,并且將對(duì)應(yīng)的 DD cache 對(duì)象加入 uncommitted 緩存中。
在事務(wù)提交或者回滾后,client 將局部緩存中的 dropped 和 uncommitted registry 清除。由于 InnoDB 引擎支持事務(wù),持久存儲(chǔ)層面的數(shù)據(jù)會(huì)通過(guò)存儲(chǔ)引擎的接口提交或回滾,不需要 client 額外操作。
在這個(gè)過(guò)程中,由于 MDL(metadata lock) 的存在,不會(huì)有其他的線(xiàn)程嘗試訪(fǎng)問(wèn)正在變更對(duì)象的 DD object,所以可以安全的對(duì) Shared_dictionary_cache 進(jìn)行操作。當(dāng) DDL 操作結(jié)束(提交或回滾),釋放 EXCLUSIVE 鎖之后,新的線(xiàn)程就可以重新從存儲(chǔ)引擎上加載新的表定義。
七 總結(jié)
MySQL data dictionary 解決了背景所述舊架構(gòu)中的諸多問(wèn)題,使元數(shù)據(jù)的訪(fǎng)問(wèn)更加安全,存儲(chǔ)和管理成本更低。架構(gòu)實(shí)現(xiàn)非常的精巧,通過(guò)大量的模版類(lèi)實(shí)現(xiàn)使得代碼能夠最大程度上被復(fù)用。多層緩存的實(shí)現(xiàn)也能顯著提升訪(fǎng)問(wèn)效率。通過(guò) client 簡(jiǎn)潔的接口,讓 Server 層和存儲(chǔ)層能在任何地方方便的訪(fǎng)問(wèn)元數(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