InnoDB缓存---InnoDB Buffer Pool

InnoDB Buffer Pool

定义

对于InnoDB存储引擎,无论用户数据仍是系统数据都是以页的形式存储在表空间进行管理的,其实都是存储在磁盘上的。git

当InnoDB处理客户端请求,须要读取某页的一条记录时,就会将这个页中的全部数据加载到内存中,再进行读写操做,当读写操做完成后,不是先将内存空间释放,而是将其缓存起来,当下次有一样的请求时,能够省去磁盘IO的开销。github

而InnoDB缓存这些页的内存就叫作Buffer Pool,(5.7.5v以前这是一块连续的内存,能够在配置文件中以innodb_buffer_pool_size动态修改其大小,这以后,以chunk为单位想操做系统申请空间,Buffer Pool由若干个chunk组成,一个chunk就是一片连续的空间)若是Buffer Pool大小大于1G时,那么能够被拆分红若干个小的独立的实例(系统变量innodb_buffer_pool_instances设置个数),缓存页的映射是有特定算法的,因此不存在重复缓存页,且在多线程访问时,互不影响。web

(TODO Buffer Pool配置注意事项+Buffer Pool的状态信息查看SHOW ENGINE INNODB STATUS算法

image_1d15r7te41q58egj1b4plh615ug7r.png-125.5kB

组成

Buffer Pool由n个控制块和n个缓存页还有一些碎片组成,控制块与缓存页一一对应。缓存

  • 缓存页:和磁盘上的页同样都是16kb大小,存放在Buffer Pool后边服务器

  • 控制块:存放了一些控制信息包含页所属的表空间编号/页号/缓存页在Buffer Pool中的位置/链表节点信息/锁信息/LSN信息等,存放在Buffer Pool前边,大小约为缓存页的5%多线程

  • 碎片:Buffer Pool中不能再分配的空间异步

管理

Buffer Pool在MySQL服务器启动时,就会完成初始化工做:申请Buffer Pool内存空间,划分若干对控制块和缓存页;其中缓存页的的使用状况会用到一些数据格式来管理,如空闲缓存页,被修改过的脏页等用到双向链表。操作系统

空闲页

free链表记录Buffer Pool中哪些缓存页是可用的,将缓存页对应的控制块做为一个节点存放在链表中,在Buffer Pool初始化时,是将全部控制块都存放的。free链表定义了一个基节点,存储了链表的头尾节点;每当须要使用一个缓存页时,都会从free链表取出一个空闲的缓存页,将控制信息填上,并将对应的缓存页节点从free链表移除。线程

缓存页查找

申明一个hash表,以表空间号+页号为key,缓存页为value存储已经被加载到缓存中的缓存页,这样就能够很快定位哪些页已经被缓存了,就无需重复为这个页申请缓存页

脏页

脏页是修改了已经被加载到Buffer Pool中的缓存页数据,致使它和磁盘上的不一致。为了将这些脏页都同步到磁盘上,建立了一个flush链表,用于存储这些脏页的信息,隔一段时间就将这些数据同步到磁盘。flush链表和free链表构造同样

刷新脏页到磁盘,有两种方式(在flush链表的页确定也在LRU链表)

  • 从LRU链表的冷数据中刷新一部分页面到磁盘BUF_FLUSH_LRU:后台线程定时从LRU链表尾部开始扫描,若是发现脏页就会刷新到磁盘
  • flush链表中刷新一部分页面到磁盘BUF_FLUSH_LIST:后台线程定时从flush链表中刷新一部分页面到磁盘
  • 刷新LRU链表尾部单页到磁盘BUF_FLUSH_SINGLE_PAGE:当用户线程准备加载的一个磁盘页到Buffer Pool,却没有空间,先查找尾部是否有能够直接释放却没有修改的缓存页,若是没有就会强制将LRU链表尾部的一个脏页同步到磁盘

缓存淘汰

Buffer Pool的大小是有限的,因此须要将一些旧的缓存页从Buffer Pool中移除,可是移除缓存页时也指望缓存集中率高

问题

为了考虑两种可能会致使缓存命中率下降状况,因此须要对这个链表作必定的设计

  1. InnoDB提供“预读”的功能,即在执行某个请求后,将它认为以后可能会读到的一些页面预先加载到Buffer Pool中,并存放到LRU链表的头部

预读read ahead

  • 线性预读(使用操做系统核心提供的AIO接口异步读取下一个区中所有的页面到Buffer Pool)
  • 随机预读(若是Buffer Pool中已经缓存了某个区的13个连续(即young区域的头1/4)的页面,不论这些页面是否是顺序读取的,都会触发一次异步读取本区中全部其的页面到Buffer Pool的请求)

这个预读到的页若是真的被访问到,是能够提升效率的,但若是没有被访问,就会浪费内存,且让存放在链表尾部的缓存被淘汰,下降了缓存命中率;

  1. 全表扫描时读取表中全部记录,这时会将该表的全部页都存放到Buffer Pool中,这样可能会让Buffer Pool被彻底覆盖,一些命中率很是高的缓存页就被淘汰了,下降了缓存命中率;
实现

LRU链表以按照最近最少使用的原则去淘汰缓存页,将这个链表分为两截(young区和old区,以使用频率高低区分,系统变量innodb_old_blocks_pct肯定old区所占比例)

当访问某个页不存在缓存页中时(初始读),将这个缓存页的控制块放到old区,并在对应的控制块中记录下访问时间,这样预读/全表扫描的不被后续访问的页面就会逐渐从old区移除,若是后续被访问时,比较当前访问时间和记录的时间是否在一个时间间隔内(系统变量innodb_old_blocks_time查看),若是不在就会把页放到young区域的头部,反之,不会移动;这样就会减小将young中使用频率较高的页给顶下去的机会。

可是频繁的移动也会产生比较大的开销,因此规定只要被访问的缓存页位于young区域的1/4的后面,才会被移动到LRU链表的头部,下降调整LRU链表的频率

相关文章
相关标签/搜索