您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

浅析MySQL的Table_cache实现

时间:2023-02-21 14:11:34  来源:微信公众号  作者:朱德润

由于本人水平有限,有错误的地方还请大家帮忙指正.

我们知道MySQL是一个插件式存储引擎的数据库,不同存储引擎的对象的元数据的存储方式是不一样的.例如:InnoDB的表的元数据信息都是存储在SYS_TABLES和SYS_INDEXES等数据字典中,数据结构也是dict_table_t 、dict_index_t等结构体,而MyISAM的表结构只有.frm文件存储.那么MySQL Server层怎样识别以及使用不同Engine的对象结构呢?

  • TABLE_SHARE

   MySQL Server层在缓存不同Engine的表对象过程中使用TABLE_SHARE的结构体,这里是不区分任何存储引擎的表结构,并且每一个表名(带模式名即库名)都一一对应一个TABLE_SHARE结构体对象.

TABLE_SHARE结构体主要成员如下:

struct TABLE_SHARE{ TABLE_CATEGORY table_category; //表的类型 ... Field **field; //表的field字段 KEY *key_info; //表定义的KEY信息(即索引信息) LEX_STRING table_cache_key; //TABLE_SHARE对象在table_cache中的key LEX_STRING db; //表所在的DB name LEX_STRING table_name; //表名 LEX_STRING path; //.frm文件路径名  // 指向每个table_cache包含该表的el地址 数组大小等于table_cache的数组大小  Table_cache_element **cache_element;  ...ulong   version;             //TABLE_SHARE的版本 如果版本变了 必须重新reopenulong mysql_version; /* 0 if .frm is created before 5.0 */ulong reclength; //记录长度ulong stored_rec_length; //存储的记录长度uint ref_count;              // TABLE 对象在使用的个数 即存在多少个TABLE对象 plugin_ref db_plugin; //存储引擎对象指针 ...}

当MySQL Server层在open table时,需要从frm文件(不区分存储引擎)中将这个表的表名、库名、所有的列信息、列的默认值、表的字符集、对应的.frm文件路径、所属的Engine、索引等信息存储到TABLE_SHARE结构体对象中,然后TABLE_SHARE对象在table_def_cache中缓存.(open_table_def中完成从frm到TABLE_SHARE写入)

  • TABLE

当我们获得TABLE_SHARE的对象之后,该如何使用TABLE_SHARE对象呢?同一时刻可能存在多个不同的session同时访问同一个表进行不同的操作,那么怎样保证每个不同的session的对象都是独立的互不影响的呢?

每个连接到MySQL Server层的thread在获得TABLE_SHARE对象之后(所有的线程可以共用一个TABLE_SHARE对象),都会创建一个TABLE结构体的对象,这个对象是该thread在使用期间独占的.(open_table_from_share )

TABLE结构体精简如下:

struct TABLE{  TABLE_SHARE   *s; //TABLE_SHARE对象指针  handler   *file; //存储引擎句柄 对存储引擎的操作通过该对象指针操作  TABLE *next, *prev; //TABLE对象前后节点指针private:  TABLE *cache_next, **cache_prev;//用于table_cache中的list链表节点指针friend class Table_cache_element; //用于访问cache_next和cache_prev两个成员public:  THD   *in_use;            //thread 对象指针  Field **field;            //表的列的存储对象 同TABLE_SHARE中的列  uchar *record[2];         //记录数据的存储地址  uchar *write_row_record;     //THE::write_row中优化使用  uchar *insert_values;        //用于INSERT ... UPDATE  ....  KEY  *key_info;           //表定义的KEY信息 同TABLE_SHARE  ....};
  • Table_cache管理

      MySQL Server层对TABLE对象的管理主要通过table_cache_manager完成,主要结构如下:

class Table_cache_element{// 缓存一个表名的所有TABLE对象 一个element对象只缓存一个表名的所有TABLEprivate:  typedef I_P_List <TABLE,                    I_P_List_adapter<TABLE,                                     &TABLE::cache_next,                                     &TABLE::cache_prev> > TABLE_list;  TABLE_list used_tables; // 正在使用的TABLE对象  TABLE_list free_tables; // 可以直接使用的TABLE 对象  TABLE_SHARE *share; // TABLE_SHARE 缓存对象  }  class Table_cache {// 缓存TABLE对象 一个Table_cache包含N个不同表名的TABLE对象 HASH m_cache; // The hash of Table_cache_element objects,Table_cache_element::share::table_cache_ke作为hash的key TABLE *m_unused_tables; // 所有unused的TABLE对象};
class Table_cache_manager{  Table_cache m_table_cache[MAX_TABLE_CACHES]; // table_cache对象数组}extern Table_cache_manager table_cache_manager; // table_cache全局管理对象
HASH table_def_cache // 缓存TABLE_SHARE的hash表

table_cache的精简架构图如下:

一个thread怎样获得缓存的TABLE* 对象:

1)根据thread_id%table_cache_instances 获得tc对象,假设为tc1

2)根据key找到对应的el对象,假设为el1

3)获得el1中free_tables的TABLE* 对象

如果已经创建了一个TABLE*对象,那怎样快速知道一个TABLE* 是属于哪个el的呢而加入到对应的used_tables链表中呢?

TABLE_SHARE存在一个el*的s数组,数组的大小为table_cache_instances个数,如下图:

假设thread获得的为tc1和el1,那么cache_element[1]存储的为tc1->el1对象.

  •  
el= table->s->cache_element[table_cache_manager.cache_index(thd->id)];
  • open_table和close_table

open_table:

接下来看MySQLServer层在open表的过程中,加载表结构.frm文件,转换TABLE对象的主要步骤.

open_table|->get_table_def_key //库名.表名  得到对应的key|->retry_share:|->Table_cache *tc = table_cache_manager::get_cache(thd) |     //首先根据thd的m_thread_id%table_cache_instances 获取table_cache_manager.m_table_cache[i] |->table = tc->get_table(thd, key, key_length, &share)|  |->el_it = tc->m_cache.find(key_str) //一个key对应一个el 一个el对应一个TABLE_SHARE* N个TABLE*|  |->if(el_it == m_cache.end()) return NULL|  |->*share = el->share|  |->if((table = el->free_tables.front()))//从el的free_tables链表获取TABLE*对象|  |   |->el->free_tables.remove(table)|  |   |->el->used_tables.push_front(table) //close_thread时候会将table从el的used_tables移除,加入free_tables|  |->return table|->get_table_share_with_discover|   |->get_table_share|      |->my_hash_search_using_hash_value从table_def_cache中HASH查找TABLE_SHARE|      |->alloc_table_share 新建share变量赋值share的frm路径信息|      |->my_hash_insert //share插入hash表table_def_cache中|      |->open_table_def |      |  |->open_binary_frm //读取.frm文件赋值engine类型、KEY、FIELD信息|      |     |->legacy_db_type= (enum legacy_db_type) (uint) *(head+3);//获得engine类型|      |     |->share->db_plugin= ha_lock_engine //根据engine类型获得plugin对象 初始化在innobase_init中完成|      |->// 若table_def_cache记录超过table_def_size 则从hash删除oldest_unused_share|->share_found:|->if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))|   |->if (share->has_old_version())|      |->release_table_share(share) // 如果TABLE_SHARE没有引用 则从table_def_cache中删除|      |->tdc_wAIt_for_old_version(lock_wait_timeout)|      |  |->TABLE_SHARE::wait_for_old_version|      |     |->m_flush_tickets.push_front(&ticket)// 增加ticket到SHARE的m_flush_tickets链表中|      |     |->thd->mdl_context->m_wait.timed_wait(thd,wait) // MDL_wait::timed_wait等待其他线程唤醒|      |->goto retry_share // 唤醒之后再次获取TABLE_SHARE|->open_table_from_share|   |->outparam->file=get_new_handler(share->db_type())|   |  |->file= db_type->create(db_type, share, alloc) |   |      |-> file->table_share=share file->ht=db_type //innobase_create_handler创建handler对象|   |  |->file->init() //handler::init()|   |->//copy share中的key、column信息赋值到TABLE中|   |->outparam->file->ha_open(TABLE,table_name,mode)|   |   |-> file->table=outparam //handler::ha_open|           |->ha_innobase::open(table_name)|              |->ib_table=open_dict_table(table_name,...,)//根据name加载innodb的系统表中的表的元数据|              |->m_prebuilt=row_create_prebuilt(ib_table,TABLE->S->reclength)//创建prebuit对象在查询数据时的元组结构|->Table_cache::add_used_table //将TABLE对象加入Table_cache中|  |->el=table->s->cache_element[this->idx] // 获取该表名在该table_cache中的el|  |->if(!el) // 如果不存在|  |  |->el= new Table_cache_element(table->s) // 创建el对象|  |  |->my_hash_insert(&m_cache, (uchar*)el) // 加入当前table_cache的el的hash链表|  |  |->table->s->cache_element[this->idx]=el // 存到TABLE_SHARE对应的el位置 其他线程访问当前table_cache使用|  |->el->used_tables.push_front(table) // TABLE对象加入el的used链表|  |->m_table_count++ |->table_found:|->thd->set_open_tables(table)|->table->init(thd, table_list)

流程解释:

    1)首先根据thd的thread_id找到对应的tc,然后根据key定位找到tc中缓存的el,如果el的share和TABLE都存在,则直接使用TABLE对象.

 

    2)如果el的share存在,无缓存的TABLE对象,则调用open_table_from_share,根据share构造TABLE对象,并加入used_tables链表.

 

   3)如果el的share不存在,则先调用get_table_share_with_discover根据frm文件构造share对象,然后再构造TABLE对象. 

 

close_table:

我们看下当一个thd一条语句执行完毕之后,对于打开的TABLE对象如何处理

mysql_execute_command|->close_thread_tables|  |->while (thd->open_tables)|     |->close_open_tables(thd, &thd->open_tables)|        |->close_thread_table|           |->if(!table->needs_reopen())ha_innobase::reset()//reset某些成员变量|           |->tc->lock();|           |->if(table->s->has_old_version()||table->needs_reopen())|           |  |->tc->remove_table(table) //TABLE* 从tc缓存中删除|           |  |->intern_close_table(table) //close TABLE->file 释放file对应的内存对象同时free_table_share释放share内存对象|           |->else|           |  |->tc->release_table(thd, table)//不close TABLE->file|           |     |->table->in_use = nullptr  |           |     |->//将TABLE* 指针对象从el->used_tables移除加入el->free_tables链表同时加入tc->m_unused_tables|           |->tc->unlock()|->MDL_context::release_transactional_locks //释放所有的MDL锁
intern_close_table(TABLE *table)|->closefrm(table, free_share=1)   |->table->file->ha_close()   |->release_table_share(table->s)      |->if(!--share->ref_count)//使用share open TABLE才会+1         |->if(share->has_old_version())            |->my_hash_delete(&table_def_cache, (uchar*) share)               |->table_def_free_entry(share)                  |->free_table_share(share)                     |->while(ticket=share->m_flush_tickets)                        |->ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED) // 唤醒其他等待old_version的thread

注:

 TABLE_SHARE::m_version在alloc_table_share初始化为refresh_version 在DDL过程中会调用TABLE_SHARE::clear_version()将m_version重置为0

从上面的过程我们可以知道:

   TABLE对象已经不仅仅是MySQL Server层的对象了,它是可以具体操作某一个存储引擎的对象,所以TABLE对象作为MySQL层和存储引擎之间的桥梁,可以直接使用TABLE对象的存储引擎的句柄执行各个公共C++ API来操作具体执行动作.

总结:

   通过对源码的浅析,我们已经基本了解了MySQL Server层怎样通过一步步获得不同存储引擎表结构的信息,希望对大家理解MySQL Server层对象的缓存有一定的帮助.



Tags:MySQL   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  Search: MySQL  点击:(6)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  Search: MySQL  点击:(11)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  Search: MySQL  点击:(10)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  Search: MySQL  点击:(24)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10  Search: MySQL  点击:(6)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  Search: MySQL  点击:(28)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  Search: MySQL  点击:(47)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  Search: MySQL  点击:(53)  评论:(0)  加入收藏
为什么高性能场景选用Postgres SQL 而不是 MySQL
一、 数据库简介 TLDR;1.1 MySQL MySQL声称自己是最流行的开源数据库,它属于最流行的RDBMS (Relational Database Management System,关系数据库管理系统)应用软件之一。LAMP...【详细内容】
2024-02-19  Search: MySQL  点击:(38)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  Search: MySQL  点击:(54)  评论:(0)  加入收藏
▌简易百科推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  爱可生开源社区    Tags:MySQL   点击:(6)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  爱可生开源社区  微信公众号  Tags:MySQL   点击:(11)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  MySQL学习  微信公众号  Tags:MySQL   点击:(10)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  字节跳动技术团队    Tags:ByteHouse   点击:(24)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10    dbaplus社群  Tags:MySQL   点击:(6)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  转转技术  微信公众号  Tags:MySQL   点击:(28)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  数据库干货铺  微信公众号  Tags:MySQL   点击:(47)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  编程技术汇    Tags:MySQL   点击:(53)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  数据库干货铺  微信公众号  Tags:MySQL   点击:(54)  评论:(0)  加入收藏
mysql索引失效的场景
MySQL中索引失效是指数据库查询时无法有效利用索引,这可能导致查询性能显著下降。以下是一些常见的MySQL索引失效的场景:1.使用非前导列进行查询: 假设有一个复合索引 (A, B)。...【详细内容】
2024-01-15  小王爱编程  今日头条  Tags:mysql索引   点击:(85)  评论:(0)  加入收藏
站内最新
站内热门
站内头条