默认情况下Buffer Pool的大小只有128M,我们可以执行如下命令来查看缓存池的大小。
show GLOBAL VARIABLES LIKE '%innodb_buffer_pool_size%';
所以通常来说,我们建议一个比较合理的、健康的比例,是给buffer pool设置你的机器内存的50%~60%左右 比如你有32GB的机器,那么给buffer设置个20GB的内存,剩下的留给OS和其他人来用,这样比较合理一些。 假设你的机器是128GB的内存,那么buffer pool可以设置个80GB左右,大概就是这样的一个规则。
innodb_buffer_pool_size = 268435456
innodb_buffer_pool_size的单位是字节,上面的配置指定了Buffer_Pool的大小为256m。同时buffer允许的最小值为5M,如果设定小于此值系统会自动修改为5M。
Buffer_Pool会被划分为若干个页面,大小与InnoDB表空间使用的大小一致默认为16KB。为了方便管理每一个缓冲页,其上添加了许多控制信息。这些控制信息包括,页所属的表空间编号、页号、缓冲页在Buffer Pool中的地址、链表节点信息等。每个缓冲页占用的内存大小是相同的,我们把每个页对应的控制信息占用的一块内存称为一个控制块。控制块与缓冲页一一对应,都存放在Buffer Pool中。其中控制块都存放在Buffer Pool中的前面,缓冲页在后面。大概如下图所示:
当剩余空间不够分配一对控制块和缓存页后,这个剩余空间即为碎片。如果把Buffer Pool的大小设置的刚刚好也可能不会产生碎片。每个控制块占用的空间大小并不包含在innodb_buffer_pool_size中。也就是说实际操作中InnoDB在向操作系统申请的内存空间会比innodb_buffer_pool_size大一些。一般大概在5%左右。
InnoDB如何能知道当从磁盘上读取一个页该放到对应Buffer Pool的哪个缓冲页中呢?此时必须有一个记录用来标识那些缓冲页是空闲的哪些缓冲页是已经被占用的。这个时候缓冲页对应的控制块就派上用场了,我们可以把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中。这个链表就可以被称为free 链表,即空闲链表。在刚完成初始化的时候Buffer Pool中,所有缓冲页都是空闲的,所以此时每个缓冲页对应的控制块都会加入到free 链表中。为了方便管理free 链表,InnoDB特意特意了一个基节点,里面存放了链表的头结点地址,尾结点地址和链表中节点的总数等信息。这里需要注意的是,链表的基节点占用的内存空间并不包含在Buffer Pool内存空间中,而是单独申请的一块内存。基节点占用内存为40字节。此后,每当需要从磁盘中加载一个页到Buffer Pool中时,就会从free 链表中取一个空闲的缓冲页对应的控制块,然后把该缓冲页对应的free链表节点移除,标识这个缓冲页已经被占用了。所
当我们修改了Buffer Pool中某个缓冲页的数据后,它就与磁盘不一致了,此时这样的缓冲页被称为脏页。这个时候我们就需要将其刷新到磁盘上的对应页达到持久化的目的,但是每有一个更新我们都立即去刷新的话这样频繁的写数据会严重影响程序性能。是否能有一种解决方案避免这种窘境的发生。当需要刷新的脏页积攒到一定量后再统一进行处理,但是此后我们又怎么能知道Buffer Pool中那些是脏页,哪些是没有修改过的页呢。如果一次性把所有的页都刷新到磁盘上,这样又会徒增很多的性能损耗是程序并不能承受的。以InnoDB不得不又创建了一个专门用来存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为一个节点加入到这个链表中。因为这个链表的节点对应的缓冲页都需要被刷新到磁盘上,所以这个链表也被称为flush链表其结构跟free 链表基本一致。注:一个缓冲页对应的控制块不可能同时出现在free 与 flush链表中。如果一个缓冲页是空闲的那它肯定不是脏页
我们开篇便知道Buffer Pool的内存大小是有限的。当我们缓存的数据页大小超过了Buffer Pool值怎么办?此时free链表中已经没有可用的空闲缓冲页了。这个时候我们不得不将一些旧的缓冲页从Buffer Pool中删除,从而能腾出空间把新的页放进来。这个时候新的问题又接踵而至了,到底该删除那些缓冲页呢?总不能随机胡乱删除吧。按照常理来说肯定要删除使用频率最少的数据,留下最近很频繁使用的。管理Buffer Pool的缓冲页其实也是这个道理。不过我们怎么才能知道那些缓冲页最近被频繁使用,那些没有使用呢。这个时候我们是否可以再创建一个链表按照频率由高到低的顺序进行排列呢?
如上所述一个简单的LRU链表缓冲淘汰机制就算完成了,但是这么简单的设定是否会存在很多问题呢?
开篇我们提到一点InnoDB在加载数据时有可能进行预读。所谓预读,就是InnoDB 认为执行当前请求时,可能会再后面读取某些页面,于是 预先把这些可能用到的页面提前加载到了Buffer Pool中。根据触发方式可以分为以下两种:
我们知道凡事都有两面性,预读如果被命中到了确实可以大大的提升语句执行效率。可是如果预读并没有被用到呢此时这些预读的页会放到LRU链表的头部。此时Buffer Pool容量不是很大,而且很多预读页面都没有被用到的话就白白占据内存空间,导致LRU链表尾部的缓冲很快被淘汰掉,因此Buffer Pool的命中率会被大幅降低。
总得分析可能降低Buffer Pool的两种情况:
Innodb 为了解决这个问题,将LRU链表按照一定比例分为两截:
在InnoDB存储引擎中我们可以通过西面命令来查看old区域在LRU链表总所占的比例:
show variables like 'innodb_old_blocks_pct';
从执行结果我们可以看出,old区域在LRU链表中所占比例是37%,大约为3/8。当然这个比例是可以设置的,我们可以再启动服务器时通过修改innodb_old_blocks_pct=xxx启动项来控制old区域在LRU链表中所占比例。这个系统变量属于全局变量,通过如下命令设置:
set global innodb_old_blocks_pct = 50 ;
优化方案:
系统规定,档次盘上某个页面在初次加载到Buffer Pool中的某个缓冲页时,该缓冲页对应的控制块会放到old区域的头部。这样一来,预读到Buffer Pool 却不进行后续访问的缓冲页就会被逐渐从old区逐出,而不会影响young区域使用比较频繁的缓冲
我们知道全表扫描是个极耗性能和时间的任务,它在系统中相对来说执行频率非常低,因为我们要尽可能的避免全表扫描。而且在执行全表扫描的过程中,即使某个页面中有很多很多条记录,尽管每读取一条记录都算是访问一次页面,但是这个过程所花费的时间也是非常短的。所以我们只需要规定,在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间与第一次访问时间在某个时间间隔内,那么该页面就不会从old区域移动到young区域的头部,否则将它移动到young区域的头部。这个间隔时间是由系统变量innodb_old_blocks-time控制的,默认值为1000ms。
show variables like 'innodb_old_blocks_time';
也就意味着对于从磁盘加载到LRU链表中的old区域的某个页来说,如果第一次喝最后一次访问该页面的时间间隔小于1s,那么改页是不会加入到young区域的。很明显,再一次全表扫描过程中,多次访问一个页面(也就是读取同一个页面中的多条记录的时间不会超过1s)。
young区并不是像我们想的那么简单,并不是每次访问直接把它移动到LRU链表的头部,这样不断的移动会造成很大的性能开销。毕竟young区的缓冲页都是热点数据。这里InnoDB设计者做了一些优化策略,规定只有被访问的缓冲页位于young区域1/4的后面时,才会被移动到LRU链表头部。这样就可以降低调整LRU链表的频率,从而提升性能。其实在开篇提到的预读机制中,Buffer Pool中有某个区的13个连续页面就会触发随机预读是不对的,其实还要求这13个页面是非常热的页面。所谓的“非常热”,指的就是整个young区域的前1/4处。
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。