1 表定义缓存
TABLE_SHARE
一个table对应一个TABLE_SHARE,而里面存储的内容5.7版本主要来自于.frm文件(.frm是mysql sql层的表的元数据信息,主要存储了表结构的定义),相当于.frm文件的缓存,8.0版本主要来自序列化字典(Serialized Dictionary Information,SDI)。
当MySQL Server层在open table时,需要从frm文件(不区分存储引擎)中将这个表的表名、库名、所有的列信息、列的默认值、表的字符集、对应的.frm文件路径、所属的Engine、索引等信息存储到TABLE_SHARE结构体对象中,然后TABLE_SHARE对象在table_def_cache中缓存.(open_table_def中完成从frm到TABLE_SHARE写入)。TABLE_SHARE对象有引用计数和版本信息,每次使用flush操作会递增版本信息。 server层表定义缓存由hash表和old_unused_share链表组成,通过hash表table_def_cache以表名为key缓存TABLE_SHARE对象,同时未使用的TABLE_SHARE对象通过old_unused_share链表链接。
TABLE
每个连接到MySQL Server层的thread在获得TABLE_SHARE对象之后(所有的线程可以共用一个TABLE_SHARE对象),都会创建一个TABLE结构体的对象,这个对象是该thread在使用期间独占的(open_table_from_share )。
TABLE结构描述表的相关信息,
- TABLE_SHARE:表定义相关的一些DD信息,如包含的字段等;
- handler:该表所使用的存储引擎接口,
- 两个TABLE指针:串起了正在操作这个TABLE对象的THD所控制的所有TABLE对象,即THD::opened_tables。
重点是要理解Table和TableShare两者之间的关系:TableShare中实际存储了物理表的元信息,这些信息来自底层表文件,而Table则是给SQL层对表的抽象,每个线程打开的每张表都会创建一个Table对象。因此,同一张表在缓存中可能会对应多个Table对象,但是其底层TableShare则只有一份。每个Table对象中有一个指针指向TableShare。这很像是文件系统中fd与inode的关系。
Table_cache_element
由于每张表可能会被不同的用户线程打开多次,因此,设计了Table_cache_element对象来维护同一张表的所有Table对象,其中包含了TABLE_SHARE对象外,还包含used_tables和free_tables两个链表。这两个链表维护的对象是TABLE。每次需要打开表时首先从缓存中找到Table_cache_element对象,然后再从free_tables中分配一个空闲TABLE对象返回给调用者。
Table_cache
Table_Cache代表了表缓存实例,在MYSQL中,可以配置多个缓存实例以缓解并发访问时的锁竞争问题。Table_cache中缓存了所有table name至Table_cache_element对象的映射关系。
Table_cache_manager
Table_cache_manager是全局的table cache管理结构,主要管理Table_cache。实现中将全局的table cache划分为多个Table_cache instance。instance数量由参数table_open_cache_instances控制。每个用户线程根据其thread_id来决定使用哪个Table_cache instance。
2 DD元数据缓存
MySQL通过TABLE对象进行表的读写等操作,对于构建TABLE对象所需的表定义相关信息,MySQL会通过Dictionary_client与DD模块进行交互获取。data dictionary 提供了统一的 client API 供 Server 层和引擎层使用,client 和底层存储之间通过两级缓存来加速对元数据对象的内存访问,两级缓存都是基于 hash map 实现的,一层缓存是 local 的,由每个 client(每个线程对应一个 client)独享;二级缓存是 share 的,为所有线程共享的全局缓存。
Table_impl
Table_impl 类中包含一个表相关的元数据属性定义,比如下列最基本引擎类型、comment、分区类型、分区表达式等。
Table_impl 继承Abstract_table_impl 中的 Column_collection m_columns就表示表的所有列集合,集合中的每一个对象 Column_impl 表示该列的元信息,包括数值类型、是否为 NULL、是否自增、默认值等。同时也包含指向 Abstract_table_impl 的指针,将该列与其对应的表联系起来。
类似的Table_impl 中也包含所有分区的元信息集合 Partition_collection m_partitions,存放每个分区的 id、引擎、选项、范围值、父子分区等。因此获取到一个表的 Table_impl,我们就可以获取到与这个表相关联的所有元信息。
Table_impl 持久化存储和访问
DD cache 中的元信息都是在 DD tables 中读取和存储的,每个表存放一类元信息的基本属性字段,比如 tables、columns、indexes等,他们之间通过主外键关联连接起来,组成 Table_impl 的全部元信息。DD tables 存放在 mysql 的表空间中,在 release 版本对用户隐藏,只能通过 INFORMATION SCHEMA 的部分视图查看;在 debug 版本可通过设置 SET debug=’+d,skip_dd_table_access_check’ 直接访问查看。
通过以上 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() 等接口。当初次获取一个表的元信息时,会调用 Storage_adapter::get() 接口,处理过程如下:
Storage_adapter::get
|--> Transaction_ro trx(thd, isolation); //开启Attachable transaction
|--> trx.otx.register_tables<T>()
|--> Table_impl::register_tables()
|--> trx.otx.open_tables() //调用 Server 层接口打开所有元数据字典表
|-->Raw_table::find_record() // 直接调用 handler 接口根据传入的 key(比如表名)查找记录
|-->handler::ha_index_read_idx_map() // index read
|--> restore_object_from_record //从读取到的 record 中解析出对应属性,调用 field[field_no]->val_xx() 函数
|--> Table_impl::restore_attributes()
//通过调用 restore_children() 函数从与该对象关联的其他 DD 表中根据主外键读取完整的元数据定义
|-->Table_impl::restore_children
|--> Abstract_table_impl::restore_children //column信息
|--> ...
`
上述在获取列和属性的对应关系时,根据的是 Tables 对象的枚举类型下标,按顺序包含了该类型 DD 表中的所有列,与上述表定义是一一对应的。因此如果我们需要新增 DD 表中存储的列时,也需要往下面枚举类型定义中加入对应的列,并且在 Table_impl::restore_attributes() / Table_impl::store_attributes() 函数中添加对新增列的读取和存储操作。
3 open_table流程分析
MYSQL每次操作表时需要通过open_table先打开表对象,open_table流程如下:
open_table
|--> open_table_get_mdl_lock //获取MDL_SHARED锁
|--> check_if_table_exists //表是否存在判断。创建表时表不存在,产生一次空读,这次空读会完整读穿DD模块直至底层存储引擎,不过由于此时相关DD信息尚未构建
|--> dd::table_exists
|--> client->acquire //通过 key 去缓存中获取元数据对象。获取的整体过程就是一级局部缓存 -> 二级共享缓存 -> 存储引擎
|--> acquire_uncommitted(key, &uncommitted_object, &dropped) //一级缓存查找
|--> m_registry_committed.get(key, &element);
|--> Shared_dictionary_cache::get // 从二级共享缓存读
|--> Shared_dictionary_cache::get_uncached //从磁盘读系统表
|--> Storage_adapter::get
|-->Open_dictionary_tables_ctx::open_tables() // 调用 Server 层接口打开所有表
|--Raw_table::find_record() // 直接调用 handler 接口根据传入的 key(比如表名)查找记录
|--> ha_check_if_table_exists
|--> Table_cache *tc = table_cache_manager.get_cache(thd)
|--> tc->get_table //从缓存获取table、table_share对象
|--> if (table) goto table_found;//如果能成功分配一个TABLE对象
|--> if (share) goto share_found; //找到 table_share
|--> else //table、table_share都没找到,需要构建table_share
|--> get_table_share_with_discover
|--> get_table_share
|--> open_table_def
|--> fill_share_from_dd
|--> prepare_default_value_buffer_and_table_share
|--> get_new_handler
|--> innobase_create_handler
|--> share_found:
|--> open_table_from_share
|--> ha_open
|--> ha_innobase::open
|--> table_found: