作者:叶盛,腾讯云数据库TDSQL开发工程师,从事数据库内核开发工作。
在MySQL中,数据字典信息内容包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。可是包含这些元数据的数据字典不仅仅存在于数据库系统表中(information_schema,mysql,sys),还存在于server层和InnoDB存储引擎中的部分文件里,比如每个表都有一个对应的.frm文件来保存表结构的信息,.opt文件用来用来记录每个库的字符等信息,.TRN和.TRG文件用来存放触发器的信息。
使用文件来存储和管理元数据有什么问题呢?
数据字典信息不同步:前面已经提到了,因为字典信息既存在于MySQL server层中,又存在于 InnoDB引擎中 中,这就导致了元数据信息的不同步和冗余等问题;
information_schema性能太差:8.0之前,information_schema表在查询过程中需要创建临时表。不仅如此,对于上百万个库表的查询,需要每次都从文件系统中读frm文件的内容来查询,会耗费大量IO;
DDL原子性问题:因为数据库元信息存放于元信息文件中、非事务性表中以及特定存储引擎的数据字典中,这导致DDL操作无法回滚,事务性和原子性都很难保证;
crash恢复问题:因DDL不是原子性的,DDL操作中途crash了就会很难回滚和恢复。
MySQL 8.0中,不再使用文件的方式来存储数据字典的信息,.frm、.trn,、.trg 和 .par文件都被彻底淘汰,所有的元数据都用InnoDB引擎来存储,这意味着MyISAM已经可以完全从MySQL数据库中剥离。从不支持事务的MyISAM存储引擎转变到支持事务的InnoDB存储引擎后,information_schema表也可以通过视图的方式优化改进,从而解决DDL操作的原子性问题。
根据MySQL官方给出的图,可以详细的了解到information_schema在新版本中的改进:
查询information_schema表时,不用再创建临时表;
不再使用文件来存储元信息,从而减少了读取frm文件的IO开销;
优化器使用数据字典表上的索引来优化查询
MySQL8.0的数据字典实现和较老的版本的数据字典实现相比有了非常显著的变化,而本文将着重从源码角度对MySQL8.0 SQL层的数据字典实现进行分析与理解。
Part1 “两级缓存+持久化”结构
整个MySQL 8.0的数据字典实现在数据字典对象分布上呈现这种三级存储的方式。
|--Dictionary_client|--Shared_dictionary_cache|--Storage_adapter
1. Dictionary_client
Dictionary_client是整个数据字典的客户端,用户对于数据字典的操作都是通过该client实现的。
Dictionary_client中维护有三个map作为该客户端私有的缓存,如果能在私有缓存命中的话,就不需要去全局公有的Shared_dictionary_cache,甚至持久化存储中获取了。
m_registry_uncommitted:
client调用store/update接口时将object放到uncommitted map中
m_registry_committed:
事务提交后,object从uncommitted map移到committed map中
m_registry_dropped:
执行drop操作后,object移入dropped map
这三个map的类型都是Local_multi_map,本质是对std::map的封装,每个client退出后,调用Auto_releaser的析构函数将每个map清空。
2. Shared_dictionary_cache
const T *m_object; Pointer to the actual object.uint m_ref_counter; Number of concurrent object usages./*全局对象的 priamry key,一个ulonglong类型的idPrimary_id_key Id_key;*/Key_wrapper<typename T::Id_key> m_id_key; The id key for the object./*全局对象的 nameItem_name_key Name_key;*/Key_wrapper<typename T::Name_key> m_name_key; The name key for the object./*辅助kye,暂时用不到Void_key Aux_key*/Key_wrapper<typename T::Aux_key> m_aux_key; The aux key for the object主要接口:
To ensure that the mutex is locked and unlocked correctly. To delete unused elements and objects outside the scope
where the mutex is locked.
/*m_free_list实际上维护了一个LRU队列,引用计数归零的element说明当前没有被使用,因此被放到free_list的尾部。可以看作是map的缓存。*/Free_list<Cache_element<T>> m_free_list; Free list.*1.在进行put操作时,我们需要根据给定的object创建新的element,此时并不知道cache中是否有该对象,执行get之后,如果找到了该element,那么该element就应该被丢弃,我们不会把它直接删除,只要m_element_pool还有空间,就会把它先存到pool中,供下次使用。2.在进行remove操作时,被remove的element也会被存入element_pool中。*/std::vector<Cache_element<T> *> m_element_pool; Pool of allocated elements.
1. template <typename K>Cache_element<T> *use_if_present(const K &key);/*对应的其实就是get,通过key返回被封装成Cache_element的object的指针,返回指针的同时该element对象的引用计数+1。*/2. void remove(Cache_element<T> *element, Autolocker *lock);/*调用父类Multi_map_base的remove_single_element()方法。*/3. void add_single_element(Cache_element<T> *element)方法。/*而put则直接调用父类Multi_map_base的add_single_element()方法。*/
class Autolocker {private:Vector containing objects to be deleted unconditionally.typedef std::vector<const T *, Malloc_allocator<const T *>>Object_list_type;Object_list_type m_objects_to_delete;Vector containing elements to be deleted unconditionally, whichhappens when elements are evicted while the pool is already full.typedef std::vector<const Cache_element<T> *,Malloc_allocator<const Cache_element<T> *>>Element_list_type;Element_list_type m_elements_to_delete;...}
注:
m_objects_to_delete->object对象不复用,用完即删。
m_elements_to_delete->element对象放回element_pool,下次使用时直接从pool中取出init后继续使用。
3. Storage_adapter(读写InnoDB)
3.1 core_ge:
Part2 查询
1. key的定义
以Primary_id_key为例:每个数据字典表都有一个id列作为主键,而Primary_id_key就是用于基于id的读操作
class Primary_id_key : public Object_key {public:Primary_id_key() {}Primary_id_key(Object_id object_id) : m_object_id(object_id) {}// Update a preallocated instance.void update(Object_id object_id) { m_object_id = object_id; }public:virtual Raw_key *create_access_key(Raw_table *db_table) const;virtual String_type str() const;bool operator<(const Primary_id_key &rhs) const {return m_object_id < rhs.m_object_id;}private:Object_id m_object_id;};
2. 从map中查询
3. 从持久化存储中查询
struct Raw_key {uchar key[MAX_KEY_LENGTH];int index_no;int key_len;key_part_map keypart_map;Raw_key(int p_index_no, int p_key_len, key_part_map p_keypart_map): index_no(p_index_no), key_len(p_key_len), keypart_map(p_keypart_map) {}};
a b c1 0 0 // keypart_map = 1,只用第一列a1 1 0 // keypart_map = 3,用a/b两列1 0 1 // keypart_map = 5,用a/c两列...
Part3 代码分布及类的继承关系
3.1 代码分布
数据字典相关代码位于sql/dd;
数据字典表定义(表结构/索引/约束等)代码位sql/dd/impl/tables;
tables路径下面主要是对数据字典表的定义,其中.cc文件就是创建表的定义,如tables.cc,其中就定义了tables这张数据字典表是如何创建的,包括表名/列的定义/索引的定义等;而与之对应的tables.h中则是一些枚举类型,用来表示各个列/索引在表中的相对位置。
对数据字典对象进行相应的操作代码位sql/dd/impl/types;
types路径下面实现了各个数据字典表从内存对象到持久化存储相互转换的内容,如restore_attributes(从持久化存储中读出数据拼出表的内存对象)store_attributes(将内存对象分别写入持久化数据字典),serialize/deserialize(内存对象到sdi文件之间的相互转换)等,其命名为xx_impl.h/xx_impl.cc;
内存对象到持久化存储的交互,读写存储引擎等代码位于sql/dd/impl/cache(包括key的定义等)。
4.2 主要类的继承关系
namespace dd {Weak_objectEntity_objectDictionary_objectTablespaceSchemaEventRoutineFunctionProcedureCharsetCollationAbstract_tableTableViewSpatial_reference_systemIndex_statTable_statPartitionTriggerIndexForeign_keyParameterColumnPartition_indexPartition_valueView_routineView_tableTablespace_fileForeign_key_elementIndex_elementColumn_type_elementParameter_type_elementObject_tableDictionary_object_tableObject_typeObject_table_definition}
dd::cache {dd::cache::Dictionary_clientObject_registryElement_mapMulti_map_baseLocal_multi_mapShared_multi_mapCache_elementFree_listShared_dictionary_cacheStorage_adapter}





