Buffer Pool
Why?
数据存储在磁盘,每次读取速度慢。
How?
-
Buffer Pool
- Innodb 存储引擎使用的缓冲池,提高数据库的读写性能。
- 读取数据时:如果数据存在于 Buffer Pool 中就直接读。
- 修改数据时:先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。
- innodb_buffer_pool_size:默认配置128MB,建议设置成可用物理内存的 60%~80%。
Buffer Pool 管理
MySQL 启动,InnoDB 会为 Buffer Pool 申请一片连续的内存空间,然后按照默认的16KB的大小划分出一个个的页,叫缓存页。
-
MySQL刚启动使用的虚拟空间大,但是物理空间小
-
InnoDB 为每一个缓存页都创建了一个控制块,控制块信息包括「缓存页的表空间、页号、缓存页地址、链表节点」
-
缓存页分类:
-
索引页
-
数据页
-
undo 页
- 开启事务后,InnoDB 层更新记录前,先记 undo log 到undo 页
-
插入缓存
-
自适应哈希索引
-
锁信息
-
空闲页管理
Why?
使用空闲页时,如何快速定位?总不能遍历吧。
How?
Free 链表(空闲链表)
- 头节点:存储Free链表首位位置和计数
- 控制块:就是Free链表的节点,记录对应缓存页的地址,
- 使用过程:从 Free链表中取一个空闲的缓存页,把该缓存页对应的控制块的信息填上,控制块从 Free 链表中移除。
脏页管理
Why?
有数据修改时,在缓存页修改,如何控制使其再同步到磁盘?
How?
Flush 链表:元素都是脏页对应的控制块
- 后台线程就可以遍历 Flush 链表,将脏页写入到磁盘。
脏页刷磁盘时机
-
redo log 日志满:主动触发脏页刷新到磁盘;
-
Buffer Pool 空间不足:需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
-
MySQL 认为空闲:后台线程会定期将适量的脏页刷入到磁盘;
-
MySQL 正常关闭之前:把所有的脏页刷入到磁盘;
如何提高缓存命中率?
Why?
Buffer Pool大小有限,如何选择合适的缓存页进行替换,提高缓存命中率?
How?
LRU算法的缺陷:
-
预读失效
- 预读取:利用空间局部性原理,MySQL 在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘 IO。
- 失效:被提前加载进来的数据页,并没有被访问,反而把有用的页替换掉了
- 解决办法:预读页停留时间要尽可能的短
-
Buffer Pool污染
-
SQL扫描了大量的数据时,Buffer Pool 里的所有页都替换出去,导致大量热数据被淘汰了
-
MySQL设计
-
old 区域:冷数据
-
young 区域:热点数据
- 只有同时满足「被访问」与「在 old 区域停留时间超过 1 秒」两个条件,才会被插入到 young 区域头部
-
规则:
-
预读的页加入 old 头部
-
old数据第一次访问记录时间,第二次访问时:
- 间隔时间小于1s,不动
- 大于1000ms,移动到young头
-
young 区域前面 1/4 被访问不会移动到链表头部
- 防止 young 区域节点频繁移动到头部
-
innodb_old_blocks_time:间隔时间,默认1000ms
-
- innodb_old_blocks_pct:默认是 37,young:old 比例63:37。