Buffer pool 图文详解 之 free 链表
- 前言
- 数据页
- 缓存页+描述信息
- 初始化 Buffer pool
- free 链表
- 获取空闲页
- 数据页是否缓存
可关注专栏 ====》MySQL 进阶知识
收藏点赞加关注
前言
Buffer pool 是 InnerDB 存储引擎的一个重要组件,MySQL 的所有 CRUD 操作都是围绕 Buffer pool 进行的。现在只知道 Buffer pool 是一个缓冲池,里面存放了磁盘数据的缓存,那么 Buffer pool 是一个什么样的结构,是如何在 SQL 执行过程中起作用的呢?
数据页
MySQL 中抽象出来的数据单位。以前我理解的数据库核心数据模型就算表+行+字段,而实际是抽象出了一个数据页的概念,把每一行的数据都存放在了数据页,数据页是 MySQL 存储中的最小单元。所以要去更新某一行数据,就需要找到这行数据所在的数据页,然后从磁盘加载到缓冲池 Buffer pool。
缓存页+描述信息
数据页加载到 Buffer pool 对应的是一个个的缓存页,而每个缓存页都会对应一个描述信息。描述信息本身也是数据块,放在缓存页的前面。描述数据(元数据)的大小相当于缓存页的 5% 左右,如果 buffer pool 的大小为 128 MB ,实际大小可能在 130 MB 左右,因为描述数据也要占用部分空间。
初始化 Buffer pool
数据库启动时,会根据配置的 Buffer pool 大小申请一块合适的内存空间,作为 Buffer pool 的内存区域,之后会按照缓存页大小和元数据大小把 Buffer pool 内存区域划分为缓存页和元数据。只是初始化时缓存页都是空的,在执行 CRUD 时会把数据页加载到缓存页中。
free 链表
现在知道缓存页都是数据库启动时初始化好了的。而在使用时只需要往缓存页写数据就行了,那么哪些缓存页是空闲的?
这时就需要一个数据结构来表示空闲的缓存页。free 链表是一个由元数据组成的双向链表结构,在链表中的每个节点都是空闲缓存页的元数据。也就是空闲的缓存页的元数据都在 free 链表中。它有一个不属于 Buffer pool 的基础节点保存着双向链的头节点地址和尾节点地址,还保存了空闲节点的数量。
实际上元数据中保存了一对双向指针,指针 free_pre 指向当前元数据的前一个元数据地址,指针 free_next 指向当前元数据的后一个元数据地址。
获取空闲页
有了 free 链表结构之后,怎么把数据页读取到缓存页?这时就可以从 free 链表中获取一个元数据找到对应的缓存页,然后把数据读到缓存页就可以了,随后把 free 中的这个元数据移除。可是元数据是 MySQL 初始化时创建的,直接删掉?那对应的缓存页岂不是没有元数据了?实际上所谓的删除就是让当前元数据的前后元数据不要再引用自己了,那么这个元数据也就从链表中移除了。
以三个元数据为例,free 链表初始状态每相邻的两个元数据都相互形成了“环”,并且链表的头尾地址保存在基础节点中。此时链表中存在三个节点两个“环”。
当链表尾部的元数据被使用,那么被使用元数据的前一个元数据不再引用它的地址,而且基础节点保存的尾部节点地址也向前移动到前一个元数据地址。此时链表中只存在两个节点一个“环”。
当链表头部的元数据被使用,那么被使用元数据的后一个元数据不再引用它的地址,而且基础节点保存的头部节点地址也向后移动到后一个元数据地址,此时链表中只存在一个节点零个“环。
当链表的所有元数据都被使用后,链表将不存在了?不,所谓的链表只是元数据中的前后指针形成的。空闲的元素据用完后,元数据并不会实际删除,只是从链表移除而已。当使用完元数据后,元数据将重新添加到链表,只不过把它的指针交给它的前后节点就行了,这相当于删除的逆向操作。
数据页是否缓存
数据库会存在一个哈希表的结构,会用表空间号+数据页号,作为一个 key,然后缓存页地址作为 value。当使用这个数据页时,就可以通过 key 去查找数据页是否已经缓存,防止数据页被重复加载。