Buffer Pool
存储引擎中的BufferPool指的是存储引擎中的一块内存,这块内存专门用于存储从磁盘文件中读取的数据,事实上,所有的SQL语句对数据的操作都是在操作这块内存中的数据,待到操作完成后由os将脏数据刷盘
为什么数据库不直接操作磁盘文件
因为操作磁盘文件是非常耗时的,会降低数据库服务器的性能
为了保证这过程中不会出现因服务器,mysql宕机等问题造成的数据丢失,存储引擎和MySQL引入了一系列机制,如redo log(记录对数据的操作)
、undo log(用于事务回滚,存储了数据被修改前的状态)
、binlog(记录对数据的操作,比如更新了哪些数据,更新后的值是什么)
,redo log commit标记(保证redo日志和binlog日志一致性)
、buffer pool脏数据刷盘
如何配置Buffer Pool的大小
默认情况下Buffer Pool
的大小为128MB,如果想自定义这个大小,可以通过配置项innodb_buffer_pool_size
来指定
数据在Buffer Pool中是如何存放的
MySQL中对数据做了一个抽象,数据页
,一个数据页会有多行数据,一个磁盘文件由多个数据页组成
当我们要对数据进行操作(更新、删除等)时,数据库会找到数据所在的数据页,然后从磁盘文件里把这行数据所在的数据页
加载到BufferPool中,那么BufferPool中存放的就是"一张张"数据页,被称为缓存页
默认情况下,磁盘文件中存放的数据页大小为16KB,即一张数据页包含16KB的数据
对于每张缓存页,都会有一个对应的描述信息,用于描述该缓存页(包括该数据页所属的表空间、数据页的编号、在BufferPool中的地址等信息)
BufferPool的大小是数据页的大小,而数据页对应的描述信息大概占数据页的5%,因此BufferPool的大小会比指定的大小大一点,多的那些部分就是用于存储这些描述信息的
BufferPool的前部存放描述数据,后部存放数据页
数据库启动时,如何初始化Buffer Pool
数据库启动时,会按照配置项innodb_buffer_pool_size
的值再加上一点,来分配一块内存,用作Buffer Pool的内存区域,然后就会在Buffer Pool中划分出描述数据和缓存页
如何知道哪些缓存页是空闲的
数据库中有针对空闲缓存页设计的free链表
,它是一个双向链表,每一个节点就是一块空闲的缓存页对应的描述数据的地址
,链表中还有一个基础节点
,它引用了链表的头节点和尾节点,并且存储了链表中有多少个描述数据节点
free链表会占用额外的内存空间吗
数据库中会维护两份free链表吗?即Buffer Pool中的描述数据和free链表中的描述数据。
不会的,它们只有一份,在Buffer Pool中的描述数据就是free链表,链表中每个节点都会有一个free_pre
和free_next
指针,指向上一个空闲的描述数据和下一个空闲的描述数据
如何将磁盘中的数据页读入Buffer Pool中的缓存页
从free链表中取一个节点,然后找到对应的缓存页地址,接着就可以将数据页读入缓存页,读入完成后,就可以将该描述数据从free链表中清理掉,具体的清理方法就是将它的free_pre
和free_next
指针置为null,然后将它的前一个节点的free_next
指向它的下一个节点,将它下一个节点的free_pre
指向它的前一个节点
如何知道数据页有没有被缓存
在对数据进行操作的时候,必须先知道这个数据页是否被缓存了,才能执行接下来的逻辑(操作缓存或者从磁盘文件中读取数据页)
在数据库中还会维护一个哈希表,其key为表空间号 + 数据页号
,value为缓存页地址
,当使用数据页时,首先会在该哈希表中查找是否有对应的数据,如果有,直接操作缓存,如果没有,就会从磁盘文件中先读取到缓存页中,然后将其放入到哈希表中,下次再使用该数据页时,就可以查到缓存了
Buffer Pool中会不会有内存碎片
会的,因为所有的缓存页和描述数据加起来的内存大小可能小于Buffer Pool的总大小,那么剩余的那块内存就是内存碎片,无法被利用
Buffer Pool中的脏数据页
脏数据页即那些被修改后与磁盘文件中的数据页内容不一致的数据页,脏数据页被修改完成后,需要被重新刷盘,那么系统如何知道哪些数据页是脏的,并且需要刷盘呢?
和free链表
一样,数据库也会维护一个flush链表
,其数据结构与free链表
一致,包括前后指针,基础节点等,当数据页被修改后,都会将其描述数据添加到该链表中,之后,系统只要读取这个链表,就能知道哪些数据页是脏的并且需要被刷盘了
如果Buffer Pool中的缓存页不够用了怎么办
Buffer Pool是有大小限制的,可以根据free链表
的节点数来判断当前还有多少剩余的空闲缓存页,如果缓存页消耗完了,该怎么办呢?
缓存命中率
缓存命中率即缓存被使用的次数
缓存淘汰
对于那些命中率较低的缓存页,数据库会对其执行缓存淘汰
,通常我们使用的是LRU(Least Recently Used)
策略,即最近最少使用
,优先淘汰掉那些最近不常被使用的缓存页
在数据库中会维护一个LRU
链表,该链表中存放了有数据的缓存页,如果一个缓存页被使用了,那么它就会移动到链表的头部,通过查询该链表,即可知道哪些缓存页符合LRU
条件的,进而淘汰掉那些在链表尾部的缓存页
缓存淘汰机制在Buffer Pool中可能带来哪些问题
由于MySQL的预读机制,当从磁盘中加载数据页到Buffer Pool中的时候可能会连带着将该数据页的附近的数据页也读入缓存页并加入到了LRU链表头部,但是此时另外一个数据页是不会被访问的,此时如果执行缓存淘汰,那么在链表尾部的原本被经常访问的数据页就要被淘汰了
哪些情况下会触发MySQL的预读机制
-
配置项
innodb_read_ahead_threshold(默认56)
,如果顺序访问了一个区里的多个数据页,并且超过了这个阈值,就会触发预读机制,把下一个相邻区的所有数据页都加载到缓存中 -
配置项
innodb_random_read_ahead(默认off)
如果Buffer Pool中缓存了一个区里的连续13个数据页,而且这些数据页都是被频繁访问的,就会触发预读机制,把这个区里的其他数据页都加载到缓存里
全表扫描
导致的缓存淘汰
如SELECT * FROM USER
这种不加任何查询条件的SQL语句执行时,会把当前表中的所有数据页都加载到Buffer Pool的缓存页中,当剩余的缓存页不足时,就会进行缓存淘汰,那么原本在LRU链表尾部的被频繁访问的缓存页就会被淘汰
为什么MySQL要提供预读机制
为了提高性能,如果一大串连续的数据页都被频繁访问,那么它附近的数据页也很有可能被访问,如果再次从磁盘文件中读取的话,需要消耗系统性能,因为I/O操作是比较消耗性能的