Buffer Pool
在对数据库执行增删改查操作的时候,因为对磁盘的随机读写操作速度非常慢。所以通过Buffer Pool缓存磁盘的真实数据。
数据页
MYSQL中抽象出来的数据单位,MYSQL把很多行数据放在一个数据页里。实际上我们更新一行数据的时候,是通过数据库找到这行数据所在的数据页,然后加载到Buffer Pool中。
默认情况下,一个数据页是16KB
缓存页
因为在Buffer Pool中存放的也是一个一个的数据页,也叫作缓存页。在默认情况下,是和磁盘上的数据页一一对应的,所以也是16KB。
缓存页的描述信息
用于描述缓存页的一些基本信息,比如数据页所属表空间、数据页的编号、在Buffer Pool中的地址等。
每个缓存页都有对应的一个描述信息,在Buffer Pool中,每个缓存页的描述信息在最前面,然后各个缓存页放在后面
描述数据大概相当于缓存页是5%。
free链表
概念引入
当读取数据页放入Buffer Pool的时候,怎么知道哪些缓存页是空闲的?
free链表是一个双向链表,每个节点是一个空闲的缓存页的描述块地址。
空间占用
- free链表只是一个逻辑上的概念,因为每个缓存页的描述数据块中维护了两个指针,free_prev和free_next,分别指向free链表的上一个节点和下一个节点,这样就串成了一个free链表。
- free链表还有一个基础节点(但是不在链表中,链表头结点的prev=null,尾结点的next=null),40个字节,存放了free链表的头结点的地址、尾结点的地址以及free链表里当前还有多少个节点。
数据页读取到Buffer Pool的过程
- 从free链表里获取一个描述数据块,获取到对应的空闲缓存页
- 把数据页读取到对应的缓存页,写入相关的描述信息到描述数据块中
- 从free链表中移除
flush链表
在执行增删改的时候,都是基于内存中的缓存页进行操作的,一旦更新了缓存页中的数据,使得和磁盘上的数据页里的数据不一致。那么这就是脏数据页。
类似于free链表,通过缓存页描述数据块中的两个指针来将脏数据页串起来。组成一个双向链表,也有一个基础节点存放头结点尾结点的地址等。
MYSQL预读机制
当从磁盘上加载一个数据页的时候,可能会连带把这个数据页相邻的其他数据页也加载到缓存中去。分为以下两种预读方式,暂时不做说明
线性预读
顺序访问了一个区里的多个数据页(默认56页),就会把下一个相邻区中的所有数据页加载到缓存中
随机预读
如果Buffer Pool中缓存了一个区的13个随机数据页,而且这些数据页是比较频繁被访问的,就会把这个区的其他数据页都加载到缓存中
LRU链表
简化版
- 当free链表已经没有空闲页的时候,所有的缓存页都塞了数据库,此时就要淘汰掉一些缓存页。
- 此时可以将一个脏数据页刷到磁盘,然后清空这个缓存页,就有了一个空闲的缓存页。但是选择哪一个脏数据页去清空,此时就要用到LRU链表。
- 当把一个数据页加载到缓存页的时候,把对应的描述数据块放到LRU链表头部。后续查询了或者修改了某个缓存页,也会把这个缓存页挪动到LRU链表头部。
- 但是MYSQL的预读机制可能会加载没人访问的数据页,如下图。
基于冷热分离的LRU链表
- 将链表按照5:3的比例分割,63%的热数据,37%的冷数据。
- 数据页第一次加载到缓存的时候,放入冷数据头部。在1s后(参数配置)访问这个缓存页,就会被加入热数据头部。
- 在热数据区域的前1/4部分缓存页被访问后不会移动到链表头部,避免浪费性能
- 有一个后台线程会定时把冷数据区域的尾部缓存页刷回磁盘,清空加入回free链表。
- 热数据区域也会在MYSQL闲暇的时候刷回磁盘
- 无空闲缓存页时从冷数据区域尾部找到一个缓存页刷回磁盘并清空成为空闲页。
Buffer Pool
并发访问Buffer Pool的性能问题
- 多线程同时访问Buffer Pool,就会同时操作同一个free链表、flush链表和lru链表。那必然要进行加锁
因为是基于内存的操作,所以很快。其次这些链表的操作,也是基于指针的操作,也不存在性能低下的可能。
多个Buffer Pool优化并发能力
给Buffer Pool分配比较大的内存,则可以设置多个Buffer Pool.如果给分配的内存小于1G,最多就只有1个Buffer Pool。
innodb_buffer_pool_instances=8
基于chunk机制动态调整Buffer Pool的大小
Buffer Pool是由多个chunk组成的,默认一个chunk的大小是128M。
分配Buffer Pool的内存8G,4个Buffer Pool实例,那么每个Buffer Pool是2G,拥有16个chunk。
- 需要动态扩容的话只需要申请一系列128MB大小的chunk就行,然后分配给buffer pool就行。
内存的分配
- Buffer Pool总共占用机器内存的50%-60%
buffer Pool总大小 = (chunk size buffer pool instance) chunk count
总结
- 根据机器的内存设置合理的buffer pool的大小,然后设置buffer pool的数量,使得chunk数量*chunk size 接近单个buffer pool的内存。充分利用内存减少内存碎片
- 每个buffer pool里的多个chunk共用一套链表数据结构。
- 后台线程定时根据lru链表和flush链表,去把一批缓存页刷入磁盘并释放,同时更新free链表
- 如果缓存页满了,无法加载自己的缓存页,就把lru链表冷数据区域的缓存页刷盘