1.缓冲池概述:
数据存在磁盘上,内存与磁盘交互以页为单位进行io操作,如果请求一次就去io一次,读取16kb,那么对数据库的压力就太大了,此时使用缓存来处理。索引、页最终都是以文件的形式存储在磁盘上,所以需要加载进内存中。
在mysql启动之后,就会去操作系统申请一块连续的内存空间,就是buffer pool,其结构如下图所示:
由上图可知,buffer pool分为主要分为以下几个部分:控制块,碎片以及缓冲页,
控制块:为了管理缓冲页(例如,缓冲页是否为空,是不是脏页等),其与缓冲页一一对应,占缓冲页5%左右内存大小
碎片:空间分配中可能会出现一些碎片
缓冲页:存储被缓冲的数据,其大小与数据页一致,都是16kb
innodb_buffer_pool_size是碎片加上所有的缓冲页大小,是可以自己定义的,大小默认是128M,如果服务器内存大,可以设置大些。
缓冲页的大小和数据页一样大,都是16kb。
怎样确定某页是否被加载进buffer pool:有一张hash表,key是表空间+页号,value存储的是缓冲页的控制块,根据表空间和页号就可以查询到缓冲页的控制块,进而可以查找到对应的控制页
2.free链表
空的缓存页对应的控制块,组成的一个双向链表,再加上基节点就是free链表。
在mysql启动之后,会向操作系统申请一个连续的空间作为buffer pool,当存储数据的时候需要知道哪一个缓冲页是空闲的,此时就需要创建一个free链表,free链表中存储的是没有使用的缓冲页的控制块,free链表中每一个控制块都是一个双向链表,同时有一个基节点,存储的是start和end,还有count,count中存储的是空闲的缓冲页的数量,start指向头元素和尾元素。
磁盘数据加载到buffer pool的流程:
- 从free链表中获取一个空的控制块
- 通过控制块,找到对应的缓冲页,把对应的数据信息记载到缓冲页中
- 把控制块从free链表中去掉,然后count-1
3.flush链表
脏页对应的控制块组成的双向链表,再加上基节点就是flush链表。(脏页是与磁盘中数据不一致的缓冲页,因为修改等操作都是基于buffer pool的,所以缓冲页中的数据会先发生变化,为了性能所以需要减少与磁盘的io操作,会在触发条件或者触发时间将所有的脏页写入磁盘中。)
刷新操作:
- 从flush链表中刷新一部分脏页到磁盘:
- 不定时:mysql根据系统的繁忙程度决定刷新,空闲时进行刷新。
- 负载很高,脏页被刷新的进度比较慢,buffer pool中没有可用的缓冲页,此时会从LRU链表的尾部查找未修改的缓冲页,如果没有则从LRU尾部往前找已修改的缓冲页刷新到磁盘,找到则抛弃该缓冲页(因为热度不高)。
- 从LRU链表的冷数据区中刷新一部分脏页到磁盘,由mysql定时执行。
4.LRU链表
LRU:Least Recently Used:最近最少使用
分为两个部分:young区(热区)和old区(冷区),young区存储的是最近访问比较多的缓冲页对应的控制块,默认占63%。因为buffer pool大小是有限的,默认128M,所以不会存储所有的数据,所以缓冲的数据有利用率,也叫命中率
mysql两个特性会导致buffer pool被刷新:
预读:将未被加载进buffer pool的页提前于查询之前先加载
线性预读:表空间中存在多个区,每个区中有64个页,如果顺序访问区时,当前区中已经有56个页被加载进了buffer pool,那么会异步加载下一个区中的全部页。
随机预读:默认是不开启的。如果一个区中有13个连续页被加载进了buffer pool(可不连续加载),那么会把当前区所有页全部加载进buffer pool。
全表扫描:select * from table;此时会将表中所有的页加载进buffer pool。
这两个特性会破坏buffer pool的数据,所以LRU分成了两个部分,在首次加载的时候将页放进old区,这样在预读的时候,只是加载页,不读取,就不会破坏young区的数据,保留了热数据
innodb_old_blocks_pct=37指定了old区占比,可通过系统参数调整。
innodb_old_blocks_time=1000ms:两次查询同一页时间间隔,如果时间间隔不大于1000ms,那么认为是全表扫描(全表扫描速度很快,所以肯定小于1000ms),此时不将该页加载到young区;如果大于1000ms,认为是正常访问,将该页加载到young区。
5.chunk和buffer pool实例
为了提高在多线程情况下buffer pool的访问速度,buffer pool可以创建多个实例,共同组成了一个buffer pool,此时的buffer pool 的大小为buffer pool实例大小*实例个数,默认是只有一个实例,当buffer pool小于1G的时候,设置实例数是不会生效的;
每次对buffer pool进行调整的时候,还需要向os申请,所以将每个实例进行拆分成多个chunk,使用的时候按照chunk来申请,将空间碎片化,方便管理。
**************此文章只是本人学习过程中的学习笔记,不做其他用途,如果有其他意见,欢迎一起讨论,谢谢,侵删*************************