searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

MySQL源码分析——Open_table流程分析

2024-09-10 09:23:36
69
0

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链表链接。
c1.png

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。

c2.png

  重点是要理解Table和TableShare两者之间的关系:TableShare中实际存储了物理表的元信息,这些信息来自底层表文件,而Table则是给SQL层对表的抽象,每个线程打开的每张表都会创建一个Table对象。因此,同一张表在缓存中可能会对应多个Table对象,但是其底层TableShare则只有一份。每个Table对象中有一个指针指向TableShare。这很像是文件系统中fd与inode的关系。

Table_cache_element

c3.png

  由于每张表可能会被不同的用户线程打开多次,因此,设计了Table_cache_element对象来维护同一张表的所有Table对象,其中包含了TABLE_SHARE对象外,还包含used_tables和free_tables两个链表。这两个链表维护的对象是TABLE。每次需要打开表时首先从缓存中找到Table_cache_element对象,然后再从free_tables中分配一个空闲TABLE对象返回给调用者。

Table_cache

c4.png

  Table_Cache代表了表缓存实例,在MYSQL中,可以配置多个缓存实例以缓解并发访问时的锁竞争问题。Table_cache中缓存了所有table name至Table_cache_element对象的映射关系。

Table_cache_manager

c5.png

  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 的,为所有线程共享的全局缓存。

1.png

Table_impl

Table_impl 类中包含一个表相关的元数据属性定义,比如下列最基本引擎类型、comment、分区类型、分区表达式等。

c6.png

Table_impl 继承Abstract_table_impl 中的 Column_collection m_columns就表示表的所有列集合,集合中的每一个对象 Column_impl 表示该列的元信息,包括数值类型、是否为 NULL、是否自增、默认值等。同时也包含指向 Abstract_table_impl 的指针,将该列与其对应的表联系起来。

c7.png

类似的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’ 直接访问查看。

c8.png

通过以上 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() 函数中添加对新增列的读取和存储操作。
c10.png

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:
0条评论
0 / 1000
dean
4文章数
1粉丝数
dean
4 文章 | 1 粉丝
原创

MySQL源码分析——Open_table流程分析

2024-09-10 09:23:36
69
0

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链表链接。
c1.png

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。

c2.png

  重点是要理解Table和TableShare两者之间的关系:TableShare中实际存储了物理表的元信息,这些信息来自底层表文件,而Table则是给SQL层对表的抽象,每个线程打开的每张表都会创建一个Table对象。因此,同一张表在缓存中可能会对应多个Table对象,但是其底层TableShare则只有一份。每个Table对象中有一个指针指向TableShare。这很像是文件系统中fd与inode的关系。

Table_cache_element

c3.png

  由于每张表可能会被不同的用户线程打开多次,因此,设计了Table_cache_element对象来维护同一张表的所有Table对象,其中包含了TABLE_SHARE对象外,还包含used_tables和free_tables两个链表。这两个链表维护的对象是TABLE。每次需要打开表时首先从缓存中找到Table_cache_element对象,然后再从free_tables中分配一个空闲TABLE对象返回给调用者。

Table_cache

c4.png

  Table_Cache代表了表缓存实例,在MYSQL中,可以配置多个缓存实例以缓解并发访问时的锁竞争问题。Table_cache中缓存了所有table name至Table_cache_element对象的映射关系。

Table_cache_manager

c5.png

  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 的,为所有线程共享的全局缓存。

1.png

Table_impl

Table_impl 类中包含一个表相关的元数据属性定义,比如下列最基本引擎类型、comment、分区类型、分区表达式等。

c6.png

Table_impl 继承Abstract_table_impl 中的 Column_collection m_columns就表示表的所有列集合,集合中的每一个对象 Column_impl 表示该列的元信息,包括数值类型、是否为 NULL、是否自增、默认值等。同时也包含指向 Abstract_table_impl 的指针,将该列与其对应的表联系起来。

c7.png

类似的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’ 直接访问查看。

c8.png

通过以上 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() 函数中添加对新增列的读取和存储操作。
c10.png

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:
文章来自个人专栏
MySQL源码学习
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0