为了缓存磁盘中的页,InnoDB的设计者在Mysql服务器启动时就向操作系统申请了一片连续的内存,名字是Buffer Pool(缓冲池)。Buffer Pool里存储的页叫缓冲页,每一页的大小也是16KB。
比如我们查看本机mysql的缓冲池大小:
show variables like "innodb_buffer_pool_size";
缓冲池内部结构
Buffer Pool结构
从磁盘上读取一个页到Buffer Pool中时,需要将页放到缓冲页的具体位置。那么这个时候就要知道哪些页是空闲的,所以可以把所有空闲的缓冲页对应的控制块作为一个节点放到free链表中。当中某一个空闲页在后面被使用(就是控制块上的关于页的信息填充好)后就会将此控制块从链表中移除。
free链表的头尾节点会被基节点记录,这个基节点占用的内存空间是40字节,他不是放在缓冲池中的,而是单独从内存中申请的一块地方。
缓冲页的哈希处理
根据上文所提,如果缓冲池中有对应的被访问过的页,那么再访问就直接拿出来,接下来就要判断是不是在缓冲池里,如果遍历那就太慢了,所以就建立了哈希表:
key:表空间号+页号
value:缓冲页控制块
在需要访问某个页的数据时,先从哈希表中根据表空间页+页号查看是否有对应的缓冲页,如果有就直接使用该缓冲页,没有就去free链表中选一个空闲的缓冲页,然后把磁盘中对应的页加载到该缓冲区的位置。
flush链表(刷新)
只要修改了缓冲池中的某个数据页,它就立即变成了脏页,修改后是需要刷新到磁盘上的,但是如果每次修改后就立即向磁盘刷新,就会造成频繁刷新,严重影响程序性能,但是如果不立即刷新,之后再刷新就不知道哪些页是否修改过。所以我们需要一个链表把哪些修改过的页的控制块链接起来。这个链表就是flush链表,此链表的块对应的脏页都是需要刷新的。
LRU链表
LRU链表的原理:
缓冲池的内存大小毕竟是有限的,那么缓存的数据肯定会满的,那么就需要去除掉一部分。这就和联系人一样,当然是留下频繁使用的,去除不频繁使用的。每次将页从磁盘加载到Buffer Pool中的缓存页时,就把该缓冲控制块作为节点放到LRU链表的头部。
还有两种情况:
1.加载到Buffer Pool中的页不一定被用到,浪费了内存空间
2.如果非常多的使用频率很低的页被加载到Buffer Pool中,可能会把使用频率高的页挤掉。
所以将LRU链表分为两部分old区域和yong区域,old区域占用整个区域的37%(通过show variables like 'innodb_old_blocks_pct' 查到结果为37)。
当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓冲页时,该缓冲页对应的控制块会放到old区域的头部,这样,预读到Buffer Pool却不进行后续访问的页面就会被逐渐从old区域逐出,这样就不会影响yong区域的缓冲页。
虽然首次页会放到old头部,但后期会被马上访问到,每次访问又会放到yong区域的头部。所以设置一个时间间隔(系统变量 innodb_old_blocks_time),如果在一次全表扫描中,多次访问同一个页面的时间小于这个系统变量的值(默认是1000ms),那么该控制块是不会移动到yong区域,如果大于这个值,就移动到yong区域。
多个Buffer Pool实例
Buffer Pool的本质是向操作系统申请一块连续的内存。在多线程环境下,访问Buffer Pool中的各种链表都需要加锁处理,在Buffer Pool特别大并且多线程并发访问量特别高的情况下,单一的Buffer Pool可能会影响请求的处理速度。所以可以将Buffer Pool拆分成若干个小的Buffer Pool,每个小的Buffer Pool称为一个实例。修改如下:
[server]
innodb_buffer_pool_instances=数量;