MySQL 深潛 - 一文詳解 MySQL Data Dictionary
一、背景
在 MySQL 8.0 之前,Server 層和存儲引擎(比如 InnoDB)會各自保留一份元數(shù)據(jù)(schema name, table definition 等),不僅在信息存儲上有著重復(fù)冗余,而且可能存在兩者之間存儲的元數(shù)據(jù)不同步的現(xiàn)象。不同存儲引擎之間(比如 InnoDB 和 MyISAM)有著不同的元數(shù)據(jù)存儲形式和位置(.FRM, .PAR, .OPT, .TRN and .TRG files),造成了元數(shù)據(jù)無法統(tǒng)一管理。此外,將元數(shù)據(jù)存放在不支持事務(wù)的表和文件中,使得 DDL 變更不會是原子的,crash recovery 也會成為一個問題。
為了解決上述問題,MySQL 在 8.0 中引入了 data dictionary 來進(jìn)行 Server 層和不同引擎間統(tǒng)一的元數(shù)據(jù)管理,這些元數(shù)據(jù)都存儲在 InnoDB 引擎的表中,自然的支持原子性,且 Server 層和引擎層共享一份元數(shù)據(jù),不再存在不同步的問題。
二、整體架構(gòu)
data dictionary 提供了統(tǒng)一的 client API 供 Server 層和引擎層使用,包含對元數(shù)據(jù)訪問的 acquire() / drop() / store() / update() 基本操作。底層實現(xiàn)了對 InnoDB 引擎存放的數(shù)據(jù)字典表的讀寫操作,包含開表(open table)、構(gòu)造主鍵、主鍵查找等過程。client 和底層存儲之間通過兩級緩存來加速對元數(shù)據(jù)對象的內(nèi)存訪問,兩級緩存都是基于 hash map 實現(xiàn)的,一層緩存是 local 的,由每個 client(每個線程對應(yīng)一個 client)獨享;二級緩存是 share 的,為所有線程共享的全局緩存。下面我將對 data dictionary 的數(shù)據(jù)結(jié)構(gòu)和實現(xiàn)架構(gòu)做重點介紹,也會分享一個支持原子的 DDL 在 data dictionary 層面的實現(xiàn)過程。
三、metadata 在內(nèi)存和引擎層面的表示
data dictionary (簡稱DD)中的數(shù)據(jù)結(jié)構(gòu)是完全按照多態(tài)、接口/實現(xiàn)的形式來組織的,接口通過純虛類來實現(xiàn)(比如表示一個表的 Table),其實現(xiàn)類(Table_impl)為接口類的名字加 _impl 后綴。下面以 Table_impl 為例介紹一個表的元數(shù)據(jù)對象在 DD cache 中的表示。
1.Table_impl
Table_impl 類中包含一個表相關(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 也是代碼實現(xiàn)中 client 最常訪問的內(nèi)存結(jié)構(gòu),開發(fā)者想要增加新的屬性,直接在這個類中添加和初始化即可,但是僅僅如此不會自動將該屬性持久化到存儲引擎中。除了上述簡單屬性之外,還包括與一個表相關(guān)的復(fù)雜屬性,比如列信息、索引信息、分區(qū)信息等,這些復(fù)雜屬性都是存在其他的 DD 表中,在內(nèi)存 cache 中也都會集成到 Table_impl 對象里。
從Abstract_table_impl繼承來的 Collection m_columns 就表示表的所有列集合,集合中的每一個對象 Column_impl 表示該列的元信息,包括數(shù)值類型、是否為 NULL、是否自增、默認(rèn)值等。同時也包含指向 Abstract_table_impl 的指針,將該列與其對應(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,存放每個分區(qū)的 id、引擎、選項、范圍值、父子分區(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;
- };
因此獲取到一個表的 Table_impl,我們就可以獲取到與這個表相關(guān)聯(lián)的所有元信息。
2.Table_impl 是如何持久化存儲和訪問的
DD cache 中的元信息都是在 DD tables 中讀取和存儲的,每個表存放一類元信息的基本屬性字段,比如 tables、columns、indexes等,他們之間通過主外鍵關(guān)聯(lián)連接起來,組成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空間中,在 release 版本對用戶隱藏,只能通過 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: tables
- Create 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=DYNAMIC
- 1 row in set (0.00 sec)
通過以上 mysql.tables 的表定義可以獲得存儲引擎中實際存儲的元信息字段。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 是訪問持久存儲引擎的處理類,包括 get() / drop() / store() 等接口。當(dāng)初次獲取一個表的元信息時,會調(diào)用 Storage_adapter::get() 接口,處理過程如下:
- Storage_adapter::get()
- // 根據(jù)訪問對象類型,將依賴的 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 中解析出對應(yīng)屬性,調(diào)用 field[field_no]->val_xx() 函數(shù)
- |--Table_impl::restore_attributes()
- // 通過調(diào)用 restore_children() 函數(shù)從與該對象關(guān)聯(lián)的其他 DD 表中根據(jù)主外鍵讀取完整的元數(shù)據(jù)定義
- |--Table_impl::restore_children()
- |--返回完整的 DD cache 對象
上述在獲取列和屬性的對應(yīng)關(guān)系時,根據(jù)的是 Tables 對象的枚舉類型下標(biāo),按順序包含了該類型 DD 表中的所有列,與上述表定義是一一對應(yīng)的。因此如果我們需要新增 DD 表中存儲的列時,也需要往下面枚舉類型定義中加入對應(yīng)的列,并且在 Table_impl::restore_attributes() / Table_impl::store_attributes() 函數(shù)中添加對新增列的讀取和存儲操作。
- 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
- };
- };
四、多級緩存
為了避免每次對元數(shù)據(jù)對象的訪問都需要去持久存儲中讀取多個表的數(shù)據(jù),使生成的元數(shù)據(jù)內(nèi)存對象能夠復(fù)用,data dictionary 實現(xiàn)了兩級緩存的架構(gòu),第一級是 client local 獨享的,核心數(shù)據(jù)結(jié)構(gòu)為 Local_multi_map,用于加速在當(dāng)前線程中對于相同對象的重復(fù)訪問,同時在當(dāng)前線程涉及對 DD 對象的修改(DDL)時管理 committed、uncommitted、dropped 幾種狀態(tài)的對象。第二級就是比較常見的多線程共享的緩存,核心數(shù)據(jù)結(jié)構(gòu)為 Shared_multi_map,包含著所有線程都可以訪問到其中的對象,所以會做并發(fā)控制的處理。
兩級緩存的底層實現(xiàn)很統(tǒng)一,都是基于 hash map 的,目前的實現(xiàn)是 std::map。Local_multi_map 和 Shared_multi_map都是派生于 Multi_map_base。
- template <typename T>
- class Multi_map_base {
- private:
- Element_map<const T *, Cache_element<T>> m_rev_map; // Reverse element map.
- Element_map<typename T::Id_key, Cache_element<T>>
- m_id_map; // Id map instance.
- Element_map<typename T::Name_key, Cache_element<T>>
- m_name_map; // Name map instance.
- Element_map<typename T::Aux_key, Cache_element<T>>
- m_aux_map; // Aux map instance.
- };
- template <typename K, typename E>
- class Element_map {
- public:
- typedef std::map<K, E *, std::less<K>,
- Malloc_allocator<std::pair<const K, E *>>>
- Element_map_type; // Real map type.
- private:
- Element_map_type m_map; // The real map instance.
- std::set<K, std::less<K>,
- Malloc_allocator<K>>
- m_missed; // Cache misses being handled.
- };
之所以叫 Multi_map_base,是因為其中包含了多個 hash map,適合用戶根據(jù)不同類型的 key 來獲取緩存對象,比如 id、name、DD cache 本身等。Element_map 就是對 std::map 的一個封裝,key 為前述幾種類型之一,value 為 DD cache 對象指針的一個封裝 Cache_element,封裝了對象本身和引用計數(shù)。
Multi_map_base 對象實現(xiàn)了豐富的 m_map() 模板函數(shù),可以很方便的根據(jù) key 的類型不同選擇到對應(yīng)的 hash map。
Shared_multi_map 與 Local_multi_map 的不同在于,Shared_multi_map 還引入了一組 latch 與 condition variable 用于并發(fā)訪問中的線程同步與 cache miss 的處理。同時對 Cache_element 對象做了內(nèi)存管理和復(fù)用的相關(guān)能力。
1.局部緩存
一級緩存位于每個 Dictionary_client (每個 client 與線程 THD 一一對應(yīng))內(nèi)部,由不同狀態(tài)(committed、uncommitted、dropped)的 Object_registry 組成。每個 Object_registry 由不同元數(shù)據(jù)類型的 Local_multi_map 組成,用于管理不同類型的對象(比如表、schema、字符集、統(tǒng)計數(shù)據(jù)、Event 等)緩存。
- class Dictionary_client {
- Object_registry m_registry_committed; // Registry of committed objects.
- Object_registry m_registry_uncommitted; // Registry of uncommitted objects.
- Object_registry m_registry_dropped; // Registry of dropped objects.
- THD *m_thd; // Thread context, needed for cache misses.
- Auto_releaser m_default_releaser; // Default auto releaser.
- Auto_releaser *m_current_releaser; // Current auto releaser.
- };
- class Object_registry {
- std::unique_ptr<Local_multi_map<Abstract_table>> m_abstract_table_map;
- std::unique_ptr<Local_multi_map<Charset>> m_charset_map;
- std::unique_ptr<Local_multi_map<Collation>> m_collation_map;
- std::unique_ptr<Local_multi_map<Column_statistics>> m_column_statistics_map;
- std::unique_ptr<Local_multi_map<Event>> m_event_map;
- std::unique_ptr<Local_multi_map<Resource_group>> m_resource_group_map;
- std::unique_ptr<Local_multi_map<Routine>> m_routine_map;
- std::unique_ptr<Local_multi_map<Schema>> m_schema_map;
- std::unique_ptr<Local_multi_map<Spatial_reference_system>>
- m_spatial_reference_system_map;
- std::unique_ptr<Local_multi_map<Tablespace>> m_tablespace_map;
- };
- template <typename T>
- class Local_multi_map : public Multi_map_base<T> {};
其中 committed 狀態(tài)的 registry 就是我們訪問數(shù)據(jù)庫中已經(jīng)存在的對象時,將其 DD cache object 存放在局部緩存中的位置。uncommitted 和 dropped 狀態(tài)的存在,主要用于當(dāng)前連接執(zhí)行的是一條 DDL 語句,在執(zhí)行過程中會將要 drop 的舊表對應(yīng)的 DD object 存放在 dropped 的 registry 中,將還未提交的新表定義對應(yīng)的 DD object 存放在 uncommitted 的 registry 中,用于執(zhí)行狀態(tài)的區(qū)分。
2.共享緩存
共享緩存是 Server 全局唯一的,使用單例 Shared_dictionary_cache 來實現(xiàn)。與上述局部緩存中 Object_registry 相似,Shared_dictionary_cache 也需要包含針對各種類型對象的緩存。與 Multi_map_base 實現(xiàn)根據(jù) key 類型自動選取對應(yīng) hash map 的模版函數(shù)相似,Object_registry 和 Shared_dictionary_cache 也都實現(xiàn)了根據(jù)訪問對象的類型選擇對應(yīng)緩存的 m_map() 函數(shù),能夠很大程度上簡化函數(shù)調(diào)用。
- class Shared_dictionary_cache {
- Shared_multi_map<Abstract_table> m_abstract_table_map;
- Shared_multi_map<Charset> m_charset_map;
- Shared_multi_map<Collation> m_collation_map;
- Shared_multi_map<Column_statistics> m_column_stat_map;
- Shared_multi_map<Event> m_event_map;
- Shared_multi_map<Resource_group> m_resource_group_map;
- Shared_multi_map<Routine> m_routine_map;
- Shared_multi_map<Schema> m_schema_map;
- Shared_multi_map<Spatial_reference_system> m_spatial_reference_system_map;
- Shared_multi_map<Tablespace> m_tablespace_map;
- };
- template <typename T>
- class Shared_multi_map : public Multi_map_base<T> {
- private:
- static const size_t initial_capacity = 256;
- mysql_mutex_t m_lock; // Single mutex to lock the map.
- mysql_cond_t m_miss_handled; // Broadcast a miss being handled.
- Free_list<Cache_element<T>> m_free_list; // Free list.
- std::vector<Cache_element<T> *>
- m_element_pool; // Pool of allocated elements.
- size_t m_capacity; // Total capacity, i.e., if the
- // number of elements exceeds this
- // limit, shrink the free list.
- }
與局部緩存可以無鎖訪問 hash map 不同,共享緩存在獲取 / 釋放 DD cache object 時都需要加鎖來完成引用計數(shù)的調(diào)整和防止訪問過程中被 destroy 掉。
3.緩存獲取過程
用戶通過 client 調(diào)用元數(shù)據(jù)對象獲取函數(shù),傳入元數(shù)據(jù)的 name 字符串,然后構(gòu)建出對應(yīng)的 name key,通過 key 去緩存中獲取元數(shù)據(jù)對象。獲取的整體過程就是一級局部緩存 -> 二級共享緩存 -> 存儲引擎。
- // 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;
- }
- }
在一級局部緩存中獲取時,會優(yōu)先去 uncommitted 和 dropped 的 registry 獲取,因為這兩者是最新的修改,同時判斷獲取對象是否已經(jīng)被 dropped。之后再會去 committed 的 registry 獲取,如果獲取到就直接返回,反之則去二級共享緩存中嘗試獲取。
Cache miss
共享緩存的獲取過程在 Shared_multi_map::get() 中實現(xiàn)。就是加鎖后直接的 hash map 查找,如果存在則給引用計數(shù)遞增后返回;如果不存在,就會進(jìn)入到 cache miss 的處理過程,調(diào)用上面介紹的存儲引擎的接口 Storage_adapter::get() 從 DD tables 中讀取,創(chuàng)建出來后依次加入共享緩存和局部緩存 committed registry 中。
- // Get a wrapper element from the map handling the given key type.
- template <typename T>
- template <typename K>
- bool Shared_multi_map<T>::get(const K &key, Cache_element<T> **element) {
- Autolocker lock(this);
- *element = use_if_present(key);
- if (*element) return false;
- // Is the element already missed?
- if (m_map<K>()->is_missed(key)) {
- while (m_map<K>()->is_missed(key))
- mysql_cond_wait(&m_miss_handled, &m_lock);
- *element = use_if_present(key);
- // Here, we return only if element is non-null. An absent element
- // does not mean that the object does not exist, it might have been
- // evicted after the thread handling the first cache miss added
- // it to the cache, before this waiting thread was alerted. Thus,
- // we need to handle this situation as a cache miss if the element
- // is absent.
- if (*element) return false;
- }
- // Mark the key as being missed.
- m_map<K>()->set_missed(key);
- return true;
- }
由于開表訪問 DD tables,構(gòu)建 DD cache object 的過程相對耗時,不會一直給 Shared_multi_map 加鎖,因此需要對并發(fā)訪問的 client 做并發(fā)控制。DD 的實現(xiàn)方法是第一個訪問的 client 會將 cache miss 的 key 加入到 Shared_multi_map的 m_missed 集合中,這個集合包含著現(xiàn)在所有正在讀取元數(shù)據(jù)的對象 key 值。之后訪問的 client 看到目標(biāo) key 值在 m_missed 集合中就會進(jìn)入等待。
當(dāng)?shù)谝粋€ client 獲取到完整的 DD cache object,加入到共享緩存之后,移除 m_missed 集合中對應(yīng)的 key,并通過廣播的方式通知之前等待的線程重新在共享緩存中獲取。
五、Auto_releaser
Auto_releaser 是一個 RAII 類,基本上在使用 client 訪問 DD cache 前都會做一個封裝,保證在整個 Auto_releaser 對象存在的作用域內(nèi),所獲取到的 DD cache 對象都會在局部緩存中存在不釋放。Auto_releaser 包含需要 release 的對象 registry,通過 auto_release() 函數(shù)收集著當(dāng)前 client 從共享緩存中獲取到的 DD cache 對象,在超出其作用域進(jìn)行析構(gòu)時自動 release 對象,從局部緩存 committed 的 registry 中移除對象,并且在共享緩存中的引用計數(shù)遞減。
在嵌套函數(shù)調(diào)用過程中,可能在每一層都會有自己的 Auto_releaser,他們之間通過一個簡單的鏈表指針連接起來。在函數(shù)返回時將本層需要 release 的對象 release 掉,需要返回給上層使用的 DD cache 對象交給上層的 Auto_releaser 來負(fù)責(zé)。通過 transfer_release() 可以在不同層次的 Auto_releaser 對象間轉(zhuǎn)移需要 release 的對象,可以靈活的指定不再需要 DD cache 對象的層次。
六、應(yīng)用舉例:inplace DDL 過程中對 DD 的操作
在 MySQL inplace DDL 執(zhí)行過程中,會獲取當(dāng)前表定義的 DD cache 對象,然后根據(jù)實際的 DDL 操作內(nèi)容構(gòu)造出新對應(yīng)的 DD 對象。然后依次調(diào)用 client 的接口完成對當(dāng)前表定義的刪除和新表定義的存儲。
- {
- 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() 過程中,會將當(dāng)前表定義的 DD cache 對象對應(yīng)的數(shù)據(jù)從存儲引擎中刪除,然后從共享緩存中移除(這要求當(dāng)前對象的引用計數(shù)僅為1,即只有當(dāng)前線程使用),之后加入到 dropped 局部緩存中。
在 store() 過程中,會將新的表定義寫入存儲引擎,并且將對應(yīng)的 DD cache 對象加入 uncommitted 緩存中。
在事務(wù)提交或者回滾后,client 將局部緩存中的 dropped 和 uncommitted registry 清除。由于 InnoDB 引擎支持事務(wù),持久存儲層面的數(shù)據(jù)會通過存儲引擎的接口提交或回滾,不需要 client 額外操作。
在這個過程中,由于 MDL(metadata lock) 的存在,不會有其他的線程嘗試訪問正在變更對象的 DD object,所以可以安全的對 Shared_dictionary_cache 進(jìn)行操作。當(dāng) DDL 操作結(jié)束(提交或回滾),釋放 EXCLUSIVE 鎖之后,新的線程就可以重新從存儲引擎上加載新的表定義。
七、總結(jié)
MySQL data dictionary 解決了背景所述舊架構(gòu)中的諸多問題,使元數(shù)據(jù)的訪問更加安全,存儲和管理成本更低。架構(gòu)實現(xiàn)非常的精巧,通過大量的模版類實現(xiàn)使得代碼能夠最大程度上被復(fù)用。多層緩存的實現(xiàn)也能顯著提升訪問效率。通過 client 簡潔的接口,讓 Server 層和存儲層能在任何地方方便的訪問元數(shù)據(jù)。