MySQL数据库缓冲池——Buffer Pool

注意:

  • 本文对Buffer Pool的分析建立在InnoDB存储引擎的基础上。
  • 文中的“缓存池”、“缓冲池”表达的是一个意思,即Buffer Pool。

查看/设置参数命令:

# 查看InnoDB的相关参数
show engine innodb status;
# 查看缓冲池大小
select @@innodb_buffer_pool_size;
# 查看数据库中缓冲池实例的数量
select @@innodb_buffer_pool_instances;
复制代码

1. Buffer Pool与内存的关系?

Buffer Pool是InnoDB引擎中的一块内存区域,它的默认大小是128M。当MySQL启动的时候就会初始化Buffer Pool,它会根据系统中所设置的innodb_buffer_pool_size大小去内存中申请一块连续的内存空间。缓存池中有多个缓存页。实际申请的内存区域比配置的值稍微大一点,因为缓冲池中的每一个缓存页都附带了一个描述数据块,后面会讲到。

2. 缓存页和数据页的关系

数据页

MySQL在执行增删改操作时首先会定位到这条数据所在的数据页,如果数据页在缓冲池中,那么直接操作并返回结果;如果不在缓存池中,会将这个数据页加载到buffer pool中,然后进行相应的操作。MySQL中一个数据页的大小为16KB。

缓存页

Buffer Pool中有一个缓存页的概念与数据页的概念相对应,大小也是16KB,还会为每个缓存页分配一些额外的空间,用来存放之前提到的描述数据块,用来描述数据页所属的表空间、数据页号等,占缓存页的15%左右,大概是800B。

3. Buffer Pool的结构

MySQL使用chunk机制来设计缓冲池,它将Buffer Pool拆分成多个大小相等的chunk块,每个chunk默认大小为128M。每个chunk中有多个缓存页和对应的描述数据块。除此之外,Buffer Pool还包括三个链表:Free链表、Flush链表、LRU链表,这三个链表是整个Buffer Pool共享的,即多个chunk间共享的。

chunk机制的好处

需要动态调整Buffer Pool大小的时候,可以直接申请新的chunk块,避免了重新申请一块大的连续空间。

4. Buffer Pool的并发性能

Buffer Pool一次只允许一个线程访问,MySQL为了保证数据的一致性,操作的时候必须对缓存池加互斥锁。为了提高并发度,在MySQL中可以设置多个Buffer Pool

5. Free链表

之前说到每个数据页都会被加载到一个缓存页中,但是加载的时候MySQL怎么判断哪些缓存页是空闲的,哪些已经被占用了呢?

Free链表就是用来解决这个问题的。Free链表是一个双向链表,它的作用是保存空闲缓存页的描述数据块。另外,Free链表还有一个基础节点,它指向Free链表的头节点和尾节点,并保存当前链表节点的个数,即空闲缓存页的个数。

当加载数据页到缓冲池时,MySQL会从Free链表中获取一个描述数据块的信息,根据该信息找到对应的缓存页,然后把数据页加载到该缓存页中,同时将链表中该描述数据块的节点移除。

6. 数据页缓存哈希表

从上一节知识中我们知道了如何判断缓存页是否空闲,由此引发了另一个问题:如何判断即将要加载的数据页是否已经存在于缓冲池中了呢?

MySQL中还有一个哈希表结构,它的作用是用来存储表空间号+数据页号,作为数据页的key,缓存页对应的地址作为value,这样在加载数据页之前就可以通过哈希表中的key来判断数据页是否已经存在于缓存中了。

表空间号 + 数据页号 = 缓存页地址
复制代码

7. Flush链表

由于MySQL的写操作是在内存中完成的,在写操作完成后,会产生在内存和磁盘中不一致的脏数据,所以会有一个I/O线程随机的时间将脏数据刷新到磁盘中。

那么这个线程怎么知道哪些是脏数据,这些脏数据又该刷新到磁盘的哪个数据页中呢?

Flush链表就是用来解决这个问题的,它的作用就是记录被修改过的数据页所在缓存页的描述数据。Flush链表也有一个基础节点,作用域Free链表中的基础节点类似。当某个脏缓存页被刷新到磁盘后,Flush链表会移除这个脏数据对应的节点,这个节点会被增加到Free链表中。

8. LRU链表

我们之前谈到了使用Free链表来维护空闲的缓存页信息,那么当Free链表的节点被使用完毕时候,此时需要加载新的数据页到缓冲区,该怎么处理呢?也就是说,Buffer Pool的内存空间不够用了。

这就需要使用到内存淘汰策略了,MySQL使用的是LRU(最近最少使用算法)链表来实现淘汰策略的。

LRU——最近最少使用算法

该算法的思想是:最近使用的数据页会在未来一段时间内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。

它的主要衡量指标是使用的时间,附加指标是使用的次数。

MySQL中LRU的实现不同于传统的LRU实现。它引入了新生代和老年代、冷数据停留时间窗口的概念。

首先介绍几个概念:预读、预读失效、缓冲池污染

预读

从磁盘中加载数据到内存时,并不是需要哪一行的数据就读取哪一行,而是以页为单位进行读取的。如果之后要读取的数据就在页中,就能省去后续的磁盘IO,提高效率。

预读的理论根据是“局部性原理”:数据访问通常都遵循“集中读写”的原则,使用一些数据时,大概率也会使用附近的数据。它表明提前加载是有效的,确实能减少磁盘IO。

使用传统的LRU算法,很可能会出现预读失效和缓冲池污染的情况。

预读失效

由于预读把数据页加载到了缓冲池,但最终这个数据页中的数据并没有被读取(除了触发把数据页加载到缓冲池的那一次操作之外),这就是预读失效。

缓冲池污染

当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池中的所有页都替换出去,导致大量热数据被置换出去,MySQL性能急剧下降,这就是缓冲池污染。

9. MySQL中LRU算法的实现

为了解决以上问题,MySQL对传统的LRU算法进行了改进。

首先,将LRU链表(为了减少数据的移动,通常使用链表实现)分为两部分:

  • 新生代
  • 老年代

懒了,直接放个链接,下次再来总结:

参考链接

www.cnblogs.com/wyq178/p/99…

www.cnblogs.com/ScarecrowAn…

www.jianshu.com/p/f9ab1cb24…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享