一、innoDB设计缓冲池目的
避免频繁访问磁盘,提高数据库读写性能。(作用与引用Cache三级缓存类似。)
二、缓冲池工作模式
读取数据:当Buffer Pool存在目标数据,就直接返回给客户端,没有再磁盘取数据。
修改数据:修改Buffer Pool中数据的页,并将其设为脏页,最后后台线程将脏页写入磁盘。
三、缓冲池基本概念
申请时间及大小
MySQL启动后向OS申请一片连续内存空间。默认128MB。
磁盘和内存交互基本单位:页。页的大小:16KB。
结构
主要为:全局内存(Global buffer)、线程内存(Thread buffer) 两大部分。
全局内存:缓冲池里面有数据缓存页、索引缓存页、锁信息页、插入缓存页等等。 此外还有重做日志缓存、额外的内存池。
线程内存:Master Thread、IO Thread、Purage Thread、Page Cleaner Thread。
缓存页与控制块
innoDB为每个缓存页创建一个控制块,控制块信息包括「缓存页的表空间、页号、缓存页地址、链表节点」
碎片空间产生原因
Buffer Pool大小固定,每个缓存页对应一个控制块 大小固定。如果设置大小不是刚好的,就会产生多余的内存空间,也就是碎片空间。
从磁盘查询一条记录,是只缓冲一条记录吗?
磁盘与内存交互基本单位是页。而且索引也只能定位到记录属于磁盘中哪个页,
将页缓冲到Buffer Pool中后再通过页目录定位到具体的哪条记录。
Buffer Poll管理
空闲页管理
当从磁盘读取数据缓存到 Buffer Poll的空闲缓存页,需要快速找到哪些缓存页是空闲的!
方法:创建Free链表,将空闲页的控制块作为节点,加入到Free链表。
缓存页空闲,则将其控制块加入到Free链表;缓存页存储数据了,则将其控制块移除Free链表。
脏页管理
当后台线程要将脏页写入到磁盘,需要快速找到哪些缓存页 是脏页!
方法:创建Flush链表,将脏页的控制块作为节点,加入到Flush链表。
后台线程直接遍历 Flush 链表,将脏页写入到磁盘。
提高磁盘命中率方法
LRU(Least recently used)算法
(根据程序局部性原理生成)
- 当访问的页在 Buffer Pool 里,就直接把该页对应的 LRU 链表节点移动到链表的头部。
- 当访问的页不在 Buffer Pool 里,除了要把磁盘中的页放入到 LRU 链表的头部,还要淘汰 LRU 链表末尾的节点。
弊端:预读失效 ;Buffer Poll污染
预读机制
据程序空间局部性原理,在加载数据页时提前将其相邻数据页加载,目的减少磁盘IO;
预读失效:不会被访问的预读页却占用了 LRU 链表前排的位置,而末尾淘汰的页,可能是频繁访问的页,这样就大大降低了缓存命中率。
解决方法:LRU 划分了2 个区域:old 区域 和 young 区域。预读的页就只需要加入到 old 区域的头部,当页被真正访问的时候,才将页插入 young 区域的头部。
young区域 存储热数据;old区域 存储冷数据;
Buffer Poll污染
Buffer Poll污染:当某个SQL语句扫描大量数据,将Buffer Poll里数据页全替换,淘汰大量热数据。再次访问时产生大量磁盘IO,使MySQL性能急剧下降。
解决方法:进入到 young 区域条件增加了一个停留在 old 区域的时间判断。
同时满足「被访问」与「在 old 区域停留时间超过 1 秒」两个条件,才会被插入到 young 区域头部
脏页写入磁盘时机
如果每次修改数据均刷入磁盘,性能会很低。因此在一定时机,批量刷盘。
MySQL宕机防止脏页丢失策略
Write Ahead Log策略。也就是先写日志,再写入磁盘。通过redo Log日志,让MySQL有崩溃恢复能力。
触发脏页刷新时机
- 当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘;
- Buffer Pool 空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
- MySQL 认为空闲时,后台线程会定期将适量的脏页刷入到磁盘;
- MySQL 正常关闭之前,会把所有的脏页刷入到磁盘;