数据都是存储在磁盘中的,单查询某条记录的时候,mysql从表空间中取出对应的页,从磁盘加载到内存,当查询完不会立刻释放,而是缓存起来.
这个用于缓存磁盘中的页的连续内存空间就是buffer pool,可以在配置文件中设置大小默认128m
buffer pool内部是由控制块和缓存页组成
控制块:保存着缓存页的属性控制信息
当我们需要存储磁盘页的时候需要知道存到哪个缓存页中,我们如何知道那个缓存页是空的呢?
free链表 mysql使用一个链表 来存储着空闲的缓存页,链表的基节点保存着链表的相关信息,mysql会创建一个hash表存储key 表空间号+页号 value为缓存也 方便使用缓存
flush链表 修改过某个缓存页 对应的控制块都放入到这个链表中 方便某个时机写回磁盘 跟free链表一样有一个基节点
毕竟buffer pool的空间是有限的,作为一个缓存来说就面临着淘汰缓存,mysql的lru算法是什么呢?
这个面临着两个问题:
1. innodb有个预读的功能,线性预读如果顺序访问了某个区的页超过了innodb_read_ahead_threshold值, 将异步将这个区的页全部加载到buffer pool中 这个值默认56,
随机预读如果buffer pool中存储着一个区的13个页面,就把这个区的页异步加载到buffer pool中 加载的页被放到链表的前面,如果预加载的页不是被频繁用到,当需要淘汰的时候就被链表后面需要被频繁用到的删除了
2. 全表扫描把这个表所有的页加载到buffer pool ,频繁的执行全表操作,就会频繁将用不到的页加入buffer pool中,淘汰那些使用频繁的页了
lru链表 为了解决这两点问题,mysql把lru链表分两个部分,一个存储使用频繁的页称为old页也叫热数据,一个存储使用频率没那么高的页称为young页也叫冷数据
数据先被存入到old部分的头部,第一次访问处于old区域的页在对应的控制器加一个访问时间,后续访问计算访问间隔,若时间间隔超过系统设定的时间范围就把这个页移到young区域,young区的页再被访问处于young区后1/4的控制块才会提到lru链表young区的头
这不是全部的lru策略,如果想了解更多请去看看源码哈~
刷新脏页到磁盘
mysql后台会每隔一段时间会刷新脏页到磁盘
那么这个刷新策略是什么呢?
buf_flush_lru 定时从lru链表尾部刷新一些脏页到磁盘
buf_flush_list 定时从flush链表刷新一些脏页到磁盘
当后台刷新速率比较慢,导致缓存页没有空余空间,会查看lru链表尾部是否有未修改过的页有的话就舍掉,如果没有就同步刷新lru尾部的一个脏页到磁盘
为了处理多线程高并发,mysql把buffer pool分成多个实例,每隔个例由chunk组成
上图是有两个buffer pool实例 每个实例有两个chunk区域(连续的内存空间)