MySQL 单点详解(09):缓冲池缓的是什么,脏页又是怎么刷回去的
面试官:buffer pool 是干什么的?加了它查询为什么就快了?
面试官:脏页什么时候刷回磁盘?刷不过来会怎么样?
缓冲池缓的不是查询结果,是数据页。
结果会失效,页不会。它只是把磁盘上的东西搬近了一点。
一、缓冲池缓的是什么
第二期说过,InnoDB 按页读写,一页 16KB。
读一行,也要把整页搬进内存。
缓冲池就是内存里腾出的一大块地方,专门放这些页。
表① 这块地方都放什么
| 区域 | 放什么 |
|---|---|
| 数据页 | 表和索引的叶子、非叶子 |
| undo 页 | 回滚要用的旧版本 |
| 变更缓冲 | 还没合并进页的索引改动 |
| 自适应哈希 | 热点等值查询的快捷入口 |
主体是数据页。一次查询要的那一页在池子里,就不用碰磁盘;不在,才去读。
磁盘读一页是微秒级往上,内存读一页是纳秒级。差的是三个数量级,不是百分之几十。
所以缓冲池命中率一掉,慢查询就成片出现。这也是它比任何 SQL 改写都更影响性能的原因。
二、LRU 为什么改成了两段
内存装不下所有页,就得淘汰。常规做法是最近最少使用先走。
但直接用这个规则会出事:一次全表扫描,几十万页涌进来,把热数据全冲走了。
InnoDB 的改法是分两段。
表② 冷热分区
| 区 | 占比 | 放什么 |
|---|---|---|
| 热区 | 八分之五 | 反复访问、真正在用的页 |
| 冷区 | 八分之三 | 新读进来的页,先在这里待着 |
新页一律先进冷区,不动热区。
而且在冷区待不满一秒的页,被访问也不许升到热区。
这一秒就是关键:全表扫描扫过的页,通常只读一次,一秒内就被淘汰,进不了热区。
真正反复用的页,隔一会儿又会被读到,那时才升上去。
同一套规则,靠一个时间门槛把"扫一遍"和"反复用"分开了。
三、脏页什么时候刷
内存里的页被改过、还没写回磁盘,这个页就叫脏页。
它不能一直脏着,也不能随时就刷。
表③ 四个刷盘时机
| 时机 | 为什么这时候刷 |
|---|---|
| 日志快写满一圈 | 不刷就没法推进,事务会被卡住 |
| 内存不够要淘汰 | 腾位置前得先把脏的写回去 |
| 系统空闲 | 趁没人用,提前分摊掉 |
| 正常关闭 | 收尾,把该落盘的都落了 |
前两个是被动,后两个是主动。
主动刷的意义在于错峰:闲的时候多刷一点,忙的时候就不用停下来等磁盘。
四、刷不过来会怎样
如果产生脏页的速度超过刷盘速度,脏页越积越多,最后触到上限。
这时候用户线程得自己下场刷脏页,语句就卡住了。
表现很有特点:SQL 平时几十毫秒,突然变成几秒;磁盘 IO 打满;过一阵自己又好了。
这叫刷脏抖动。它跟慢查询长得像,但原因完全不同,加索引没用。
常见的两个诱因:一是突然的大批量写入,二是缓冲池给小了,热数据本来就装不下。
还有一种是长事务,它让 undo 一直不释放,间接把池子占住。
五、面试官会往下问的三层
第一层:变更缓冲是干什么的?
写普通索引时,那一页可能不在内存里。为了改一个字段去磁盘读一整页,很亏。
所以先把改动记在变更缓冲里,等这一页因为别的原因被读进来时,再一起合并。
它省的是随机读,代价是这块内存不能给数据页用。
第二层:缓冲池是不是越大越好?
大部分情况是,一般给到物理内存的一半到七成。
但太大也有代价:机器重启后要花很久预热,这段时间性能很差;而且刷脏时一次要处理的量也更大。
第三层:怎么知道命中率高不高?
看状态里的命中次数和读取次数,两者一比就是命中率。
生产上更实用的判断是看磁盘读的量:业务量没涨,读却在涨,基本就是池子不够或者被大查询冲了。
收尾
这期压成一句:缓冲池把页搬进内存,两段 LRU 挡住全表扫描的冲击,脏页靠错峰刷盘,刷不动就会抖动。