转载 https://www.zhihu.com/question/19866767/answer/14942009mysql
http://whuai.blog.51cto.com/3539000/862197算法
在MYSQL中,一样有不少类型的系统对象,包括表、视图、存储过程、存储函数等,但因为MYSQL的插件式存储引擎及其它实现方面的特色,其每一种对象的缓存方式都不一样,或者说这些对象的缓存不是经过一种统一的方式来管理的,每一种对象的缓存都是有本身的特色,而且缓存的内容也有很大的差别,下面先只叙述一下表对象的缓存方式。sql
表字典对象缓存,顾名思义,是将某个表对象的字典信息(定义内容)缓存到内存中,用来提升对表访问的效率。在某一个表第一次被访问过以后,在服务器没有关闭而且表定义没有被修改的条件下,访问这个表时,只须要从内存中找到这个已经缓存起来的对象而且作相应操做便可,而不须要再次从系统表中读取它的定义而且解析,而后再作相应的操做。数据库
当某一个用户要查询某一个表的数据时,系统首先会找到这个表。上面已经提到过,由于MYSQL实现了表的缓存,因此首先会从缓存中寻找这个表,表字典对象的缓存是经过HASH表来管理的,MYSQL系统中,专门有一个HASH表(源代码中的名字是table_def_cache)用来存储组织表对象。缓存
因此首先经过表的名字(包括了模式名)来构造一个HASH键值(KEY),用来从HASH表中搜索对象。安全
可是对于表对象的缓存,不仅是简单的将一些表的定义经过HASH存储起来就算完了,那这样的话缓存可能没有任何意义,或者说局限性很是大,这样可能致使一个用户在表对象上作了什么标志或者修改等都会影响到其它用户,这种影响是不可预期的,更重要的缘由是,MYSQL是插件式的数据库,每个用户获得表对象以后还须要将表实例化,这个实例化的对象只有本身才能使用,因此不是简单的全部用户都使用同一个缓存对象便可完成的。它的缓存实际上是用了一种能够称为“共享私有化缓存”,看上去这个说法是矛盾的,其实并非这样的,它在缓存过程当中用到一个叫TABLE_SHARE的结构体,一个这个结构体惟一对应MYSQL中的一个表对象,这里是不区分任何存储引擎的,它实际上就是对具体一个表的定义的翻译或映射,也就是说当须要打开一个表的时候,这个表首先是在MYSQL的系统表中存储的(固然系统表是分不一样的存储引擎的,不一样的存储引擎有本身的系统表,这里所说的MYSQL的系统表应该是一种统称,实际上是具体某一个存储引擎的系统表),若是要使用了,首先须要从系统表中将这个表的全部信息都读入到内存中来,这些信息包括表名、模式名、全部的列信息、列的默认值、表的字符集、对应的frm文件的路径、所属的存储引引擎(MYSQL中的表能够单独定义本身的存储引擎)、主键等等,固然还有不少其它信息,全部这些信息读入内存中的时候首先就是经过结构体TABLE_SHARE来存储的,至关于这个结构体是一个表对象缓存的第一层,同时从名字就能够看出,这个结构体是因此用户均可以共享的一个表对象,因此它是静态的,不容许修改的(内存中),从系统表中读取进来以后直到这个表从缓存中删除,中间不会作任何的修改。服务器
那么用户要访问一个表,只是构造了TABLE_SHARE是远远不够的,并且这个结构体对象也不是直接给用户使用的对象,在构造了这个结构体以后,首先须要将其缓存起来,由于这个结构体就是咱们这里讨论的核心,它就是咱们要缓存的对象,因此首先须要根据上面计算获得的KEY将这个表对象缓存到table_def_cache中,这个缓存操做到这里就结束了。架构
可是若是这个问以前已经被访问过了,那么就不须要再像上面同样构造这个共享结构体了,而是直接经过HASH的KEY值在table_def_cache中找到这个共享结构体便可。并发
从上面的叙述中知道,当系统获得一个SHARE对象以后,系统会真正的构造一个新的对象交给当前的操做,这个对象上面已经说过了,确定不是TABLE_SHARE,由于这个是缓存对象,它是静态的,只读的,真正与用户交互的是TABLE_SHARE的一个衍生品,它对应的结构体名字为TABLE,它是真正的在操做中被使用的对象,那么是如何从TABLE_SHARE变为TABLE的呢?oracle
其实这两个结构体的不少成员是相同的,而且能够直接复制过去,上面已经说了,TABLE_SHARE是一个静态的缓存对象,那么相对而言,TABLE就能够称做是一个相对动态的、一个正在进行一些操做的实例了,TABLE中有一个成员就是直接指向了TABLE_SHARE的;还有一些成员好比record,这个是用来构造插入操做中的一条记录的,这个系统会根据这个表定义的每个列及其数据类型等提早构造好;field用来存储全部这个表中的列信息的,这个信息实际上是彻底将SHARE中的信息克隆过来的。其它的一些小的细节就不叙述了,不过还有两个很重要的东西必需要说一下。
由于上面已经提到了,TABLE这个对象是一个动态的,被实例化的对象,它至关因而一个被打开的表,它已经不是在MYSQL的上层了,而是具体到了某一个存储引擎了,因此这里还须要对这个对象构造它的有关存储引擎的信息而且打开这个表。
由于MYSQL是一个插件式的数据库管理系统,对于表对象的管理,MYSQL层与存储引擎层就是在这里分开的,TABLE算是它们之间的桥梁,下层是存储引擎,上层就是MYSQL了,对于一个MYSQL的存储引擎,都要提供一些公共的接口来驱动其存储引擎,这些接口包括:close_connection、savepoint_set、savepoint_rollback、savepoint_release、commit、rollback、create(建立句柄)、ha_create (建立一个表)、ha_open(打开表)、ha_close(关闭表)、ha_write_row(插入一条记录)、ha_delete_row(删除一条记录)、ha_reset(将实例恢复到新打开的状态)等操做,这些接口都是上层调用来操做对应的存储引擎的,也能够被称做是MYSQL与存储引擎之间交流的通道。
那么从SHARE到TABLE之间的过渡或者叫作SHARE的实例化过程,首先就须要调用函数create来建立一个对应的存储引擎句柄,建立以后就经过函数ha_open来打开这个表,打开表主要是对这个新建立的存储引擎句柄进行一些初始化操做。在打开以后,这个表的实例化也就算完成了,而这个已经被打开的实例句柄就挂在TABLE结构体中,从这里也能够看出,TABLE是与一个操做对应的实例化的对象,它只能在同一时间内被一个操做所使用。
在被实例化以后,这个就能够直接与存储引擎进行交互了,好比插入一条记录,能够直接调用TABLE下面已经被实例化的存储引擎句柄下的函数ha_write_row便可。
当一个操做完成以后,它所实例化的表就不须要了,此时系统不是将这个本地的实例直接释放掉,而是将其保存下来了,保存下来是为了下次某一个用户再次访问这个表的时候不须要再次进行实例化了,直接拿过来用便可,固然可能须要一些额外的操做,好比将实例状态恢复,调用函数ha_reset便可。
系统保存实例是直接将其放在SHARE的一个free_tables链表中,但首先要从used_tables链表上摘下来,这两个链表都是用来保存这个表的全部实例的,used_tables用来存储正在使用的实例,free_tables用来存储全部当前未使用的实例,有可能在并发比较高的状况下,可能在used_tables中有多个,但free_tables中没有,都执行完成以后则相反,那么若是此时再有用户又操做这个表,系统能够直接从free_tables找一个拿来用便可。
如今能够知道,在MYSQL中,表对象的缓存实际上是用两个部分,一部分是SHARE的缓存,也就是说多个不一样的表的SHARE对象的缓存;另外一部分就是每个SHARE结构被实例化以后的实例对象的缓存,MYSQL用来管理缓存空间大小的方法是经过计数来实现的,默认状况下,系统中总的SHARE个数不能超过400个,全部SHARE的全部表实例的个数也不能超过400个。
上面提到的都是关于表对象SHARE结构的缓存,既然是缓存,确定相应的有它被删除或者淘汰的问题,固然在这里也不例外。那么在什么状况下SHARE结构会被淘汰或者删除呢?很明显,若是只是对这个表进行增删改等没有涉及到修改表定义的操做,SHARE是不会被删除的,只有可能会被淘汰,由于若是查询太多表的话,表对象缓存个数是有限制的,当到达这个数目以后,系统会自动将一些不常用的SHARE淘汰掉,这个很容易理解。
那么通常状况下,只有对表结构、依赖关系、表定义等方面进行修改以后,由于这个表的版本被更新了,若是继续将其缓存的话是不安全的,或者是错误的,或者致使一些不可预知的问题,因此这种状况下这个表对象的缓存SHARE对象必需要从缓存中删除,同时要删除它上面因此被实例化的表对象缓存结构,这样当其它用户在等待表对象的修改操做完成以后(由于修改过程当中这个表是被上了锁的,进行操做须要等待),又一次像上面叙述的同样,首先是从缓存中找这个表的缓存对象,若是找不到的话再从数据字典(系统表)中读取进来,而后继续操做便可。
到这里关于表的缓存及一些其它的内容就叙述完了。
总结:
1)上面提到的表的缓存机制有很大的好处的,由于它不是全字典缓存(全字典缓存的意思就是在数据库启动时将全部的数据字典信息都一次性载入内存中来,这样在使用过程当中就效率很是高,但在DDL操做方面有很大的不足),它是用到的时候再载入,修改以后直接删除有可能再从新载入,这样的实现方式减小了DDL操做或DDL的回滚致使的字典缓存维护工做的代价。
2)有效的利用了内存空间,由于能够经过设置表对象缓存空间的大小来控制内存的使用状况,同时只有用到的对象才会被载入到内存中,提升了内存的利用率。
3)上面所叙述的MYSQL表缓存实现方案虽说是比较先进的,可是在效率方面仍是有些优化的空间的,好比上面提到的,在用来控制缓存空间大小是根据实例的个数来计算的,在系统中默认最大值是400个,若是超过这个值系统会自动淘汰一些不经常使用的实例,可是若是一个表的定义很是大,那么并发状况下,有可能会创建不少个实例,假设接近400个,那么这样算下来有可能会将操做系统的内存用光,这个是不可控制的,也是不可预期的。对于SHARE的缓存也是同样,若是一个用户访问了不少不一样的定义很大的表,也会有一样的问题。
4)从上面也能够看出,为了实现插件式的数据库,其实仍是有一些效率的代价的,在表的缓存方面,中间加入了一层SHARE的缓存,真正用到的时候还须要实例化,由于每个用户的操做及不一样时间的状态都是不一样的,因此每个用户必需要再在SHARE的基础上实例化一个新的对象出来,这样就给内存、CPU带来了必定程序上的浪费及压力。
问题的解决:
1)SHARE缓存:我我的认为有一个更好的办法来很精确经过具体的空间大小来管理表缓存空间,由于对于SHARE而言,它是静态的,它是个结构体,经过使用计数来控制内存的使用,有可能会形成内存用光的状况,那么对于SHARE对象,彻底能够把它流式化(扁平化),也就是说等把这个结构体的大小计算出来,申请相应的空间,将结构体中的全部信息都按照固定的顺序写入到这块内存中,那么这样一个SHARE所占的空间大小就固定了,这样能够彻底经过设置空间大小来管理表对象缓存空间了,这样上面提到的内存用光的问题就天然解决了,固然这个大小须要根据计算机的内存大小合理的设置,至少不会出现不可预料的问题。
2)TABLE缓存:TABLE实例的缓存一样存在上面的问题,解决方案与上面的思想差很少,由于这个对象是一直被用的,它是一个实例,因此就不能直接像上面同样,将其流式化,而是能够经过申请一片链接的空间,这个实例中全部的指针或者其成员的值都指向(有可能要对齐)这个空间中的指定位置,这样这个结构体的使用没有任何改变,但其占用的空间大小是固定的,一样能够经过用户手动设置TABLE实例缓存空间的大小来管理缓存空间,这样也避免了表定义太大致使内存用光的问题。
本文出自 “怀瑾握瑜” 博客,请务必保留此出处http://whuai.blog.51cto.com/3539000/862197