MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,默认128M,可通过从参数修改。
[server]
innodb_buffer_pool_size = 268435456
控制块包括该页所属的 表空间编号、页号、缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息。**控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 后边**
free链表:所有空闲缓存页对应的控制块作为节点形成的链表。
链表的基节点:包含链表的头节点地址,尾节点地址,链表节点的数量;
需要从磁盘中加载一个页到 Buffer Pool 中时,从 free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上(就是该页所在的表空间、页号之类的信 息),然后把该缓存页对应的 free链表 节点从链表中移除,表示该缓存页已经被使用了~
表空间号 + 页号 作为 key , 缓存页 作为 value 创建一个哈希表;
需要访问某个页的数据 时,先从哈希表中根据 表空间号 + 页号查有没有对应的缓存页,如果有,直接使用该缓存页就好,如果没有,那就从 free链表 中选一个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置。
1、如果该页不在 Buffer Pool 中,在把该页从磁盘加载到 Buffer Pool 中的缓存页时,就把该缓存页对应的 控制块 作为节点塞到链表的头部。
2、如果该页已经缓存在 Buffer Pool 中,则直接把该页对应的 控制块 移动到 LRU链表 的头部。 只要用到某个缓存页,就把该缓存页调整到 LRU链表的头部,这样 LRU链表 尾部就是最近最少 使用的缓存页,所以当 Buffer Pool 中的空闲缓存页使用完时,淘汰链表尾部缓存页。
1、InnoDB会进行预读:
① 线性预读:顺序访问某个区的页面超过(innodb_read_ahead_threshold =56),异步读取一个区中的全部页面。
② 随机预读:buffer pool 缓存某个区的13个页面(无论顺序),触发异步读取本区全部页面(innodb_random_read_ahead 默认关闭)。
2、全表扫描
3 总结 降低Buffer Pool的情况
1、加载到 Buffer Pool 中的页不一定被用到。(预读)
2、如果非常多的使用频率偏低的页被同时加载到 Buffer Pool 时,可能会把那些使用频率非常高的页从 Buffer Pool 中淘汰掉。(全表扫描)
1、一部分存储使用频率非常高的缓存页叫做 热数据 ,或者称 young区域 。
2、一部分存储使用频率不是很高的缓存页叫做 冷数据 ,或者称 old区域 。
**我们是按照某个比例将LRU链表分成两半的,不是某些节点固定是young区域的,某 些节点固定是old区域的,随着程序的运行,某个节点所属的区域也可能发生变化**
①预读场景解决:初次加载到Buffer Pool中的某个缓存页时,该缓存页对应 的控制块会被放到old区域的头部。
②针对全表扫描:old 区域的缓存页进行第一次访问时,就在它对应的控制块中 记录下来这个访问时间,如果最后一次访问时间与第一次访问的时间在某个时间间隔内(很明显在一次 全表扫描的过程中,多次访问一个页面中的时间不会超过 1s ),该页面就不会被 从old区域移动到young区域的头部,否则将它移动到young区域的头部。***如果一个old区页面第二次访问与第一次访问在时间间隔内(短时间内第二次访问)则不移动到young区***
被访问的缓存页位于 young 区域的 1/4 的后边,才被移动到 LRU链表头部,降低调整LRU频率。
后台有专门的线程每隔一段时间负责把脏页刷新到磁盘,两种路径:
1、BUF_FLUSH_LRU:从 LRU链表 的冷数据中刷新一部分页面到磁盘。
2、BUF_FLUSH_LIST: 从 flush链表 中刷新一部分页面到磁盘。
3、BUF_FLUSH_SINGLE_PAGE: 后台线程刷新脏页的进度比较慢,导致Buffer Pool没有内存,从LRU链表尾部释放未修改页面(刷新单个页面的方式)。
通过参数innodb_buffer_pool_instances配置,默认为2;
当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的,InnoDB会默认把 innodb_buffer_pool_instances 的值修改为1。
一个 Buffer Pool 实例 其实是由若干个 chunk 组成的,一个 chunk 就代表一片连续的内存空间,里边儿包含了若干缓存页与其对应的控制块:
innodb_buffer_pool_chunk_size只能在服务器启动时指定,服务器运行过程中是不可以修改
1、innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数(这主要是想保证每一个 Buffer Pool 实例中包含的 chunk 数量相同)。
2、innodb_buffer_pool_size 的值必须是2G 的整数倍。假设指定innodb_buffer_pool_chunk_size 的值是 128M , innodb_buffer_pool_instances 的值是16 ,那么这两个值的乘积就是 2G。
1. 磁盘太慢,用内存作为缓存很有必要。
2. Buffer Pool 本质是InnoDB向操作系统申请的一段连续内存空间,innodb_buffer_pool_size调整大小。
3. Buffer Pool 向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后, Buffer Pool 剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为 碎片 。
4. InnoDB 使用了许多 链表 来管理 Buffer Pool 。
5. free链表 中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到 Buffer Pool 时,会从 free链表 中寻找空闲的缓存页。
6. 哈希表: key 表空间号 + 页号,value 缓存页作为 快速定位某个页是否被加载到 Buffer Pool 。
7. flush链表:Buffer Pool 中被修改的页称为 脏页 ,脏页并不是立即刷新,而是被加入到 flush链表 中,待之后的某个时刻同步到磁盘上。
8. LRU链表 分为 young 和 old 两个区域,可以通过 innodb_old_blocks_pct 来调节 old 区域所占的比例。首次从磁盘上加载到 Buffer Pool 的页被放到 old 区域的头部,在innodb_old_blocks_time 间隔时间内访问该页不会把它移动到 young 区域头部。在 Buffer Pool 没有可用的空闲缓存页时,会首先淘汰掉 old 区域的一些页。
9. Buffer Pool多实例:我们可以通过指定 innodb_buffer_pool_instances 来控制 Buffer Pool 实例的个数,每个 Buffer Pool 实例中都有各自独立的链表,互不干扰。
10. 自 MySQL 5.7.5 版本之后,可以在服务器运行过程中调整 Buffer Pool 大小。每个 Buffer Pool 实例由若干个 chunk 组成,每个 chunk 的大小可以在服务器启动时通过启动参数调整。
11. 可以用下边的命令查看 Buffer Pool 的状态信息:SHOW ENGINE INNODB STATUS\G