扫描下方二维码了解 专栏详情
本文来自狸猫技术窝专栏《从零开始带你成为 MySQL 实战优化高手》, 是作者开放的试读文章
作者: 救火队队长, 阿里资深技术专家
1, 数据库启动的时候, 是如何初始化 Buffer Pool 的?
现在我们已经搞明白一件事儿了, 那就是数据库的 Buffer Pool 到底长成个什么样, 大家想必都是理解了
其实说白了, 里面就是会包含很多个缓存页, 同时每个缓存页还有一个描述数据, 也可以叫做是控制数据, 但是我个人是比较倾向于叫做描述数据, 或者缓存页的元数据, 都是可以的.
那么在数据库启动的时候, 他是如何初始化 Buffer Pool 的呢?
其实这个也很简单, 数据库只要一启动, 就会按照你设置的 Buffer Pool 大小, 稍微再加大一点, 去找操作系统申请一块内存区域, 作为 Buffer Pool 的内存区域.
然后当内存区域申请完毕之后, 数据库就会按照默认的缓存页的 16KB 的大小以及对应的 800 个字节左右的描述数据的大小, 在 Buffer Pool 中划分出来一个一个的缓存页和一个一个的他们对应的描述数据.
然后当数据库把 Buffer Pool 划分完毕之后, 看起来就是之前我们看到的那张图了, 如下图所示.
只不过这个时候, Buffer Pool 中的一个一个的缓存页都是空的, 里面什么都没有, 要等数据库运行起来之后, 当我们要对数据执行增删改查的操作的时候, 才会把数据对应的页从磁盘文件里读取出来, 放入 Buffer Pool 中的缓存页中.
2, 我们怎么知道哪些缓存页是空闲的呢?
接着我们来看下一个问题, 当你的数据库运行起来之后, 你肯定会不停的执行增删改查的操作, 此时就需要不停的从磁盘上读取一个一个的数据页放入 Buffer Pool 中的对应的缓存页里去, 把数据缓存起来, 那么以后就可以对这个数据在内存里执行增删改查了.
但是此时在从磁盘上读取数据页放入 Buffer Pool 中的缓存页的时候, 必然涉及到一个问题, 那就是 哪些缓存页是空闲的?
因为默认情况下磁盘上的数据页和缓存页是一 一对应起来的, 都是 16KB, 一个数据页对应一个缓存页.
所以我们必须要知道 Buffer Pool 中哪些缓存页是空闲的状态.
所以数据库会为 Buffer Pool 设计一个 free 链表 , 他是一个双向链表数据结构, 这个 free 链表里, 每个节点就是一个空闲的缓存页的描述数据块的地址, 也就是说, 只要你一个缓存页是空闲的, 那么他的描述数据块就会被放入这个 free 链表中.
刚开始数据库启动的时候, 可能所有的缓存页都是空闲的, 因为此时可能是一个空的数据库, 一条数据都没有, 所以此时所有缓存页的描述数据块, 都会被放入这个 free 链表中
我们看下图所示
大家可以看到上面出现了一个 free 链表, 这个 free 链表里面就是各个缓存页的描述数据块, 只要缓存页是空闲的, 那么他们对应的描述数据块就会加入到这个 free 链表中, 每个节点都会双向链接自己的前后节点, 组成一个双向链表.
除此之外, 这个 free 链表有一个基础节点, 他会引用链表的头节点和尾节点, 里面还存储了链表中有多少个描述数据块的节点, 也就是有多少个空闲的缓存页.
3,free 链表占用多少内存空间?
可能有的人会以为这个描述数据块, 在 Buffer Pool 里有一份, 在 free 链表里也有一份, 好像在内存里有两个一模一样的描述数据块, 是么?
其实这么想就大错特错了.
这里要给大家讲明白一点, 这个 free 链表, 他本身其实就是由 Buffer Pool 里的描述数据块组成的, 你可以认为是每个描述数据块里都有两个指针, 一个是 free_pre, 一个是 free_next, 分别指向自己的上一个 free 链表的节点, 以及下一个 free 链表的节点.
通过 Buffer Pool 中的描述数据块的 free_pre 和 free_next 两个指针, 就可以把所有的描述数据块串成一个 free 链表, 大家可以自己去思考一下这个问题. 上面为了画图需要, 所以把描述数据块单独画了一份出来, 表示他们之间的指针引用关系.
对于 free 链表而言, 只有一个基础节点是不属于 Buffer Pool 的, 他是 40 字节大小的一个节点, 里面就存放了 free 链表的头节点的地址, 尾节点的地址, 还有 free 链表里当前有多少个节点.
4, 如何将磁盘上的页读取到 Buffer Pool 的缓存页中去?
好了, 现在我们可以来解答这一篇文章的最后一个问题了, 当你需要把磁盘上的数据页读取到 Buffer Pool 中的缓存页里去的时候, 是怎么做到的?
其实有了 free 链表之后, 这个问题就很简单了.
首先, 我们需要从 free 链表里获取一个描述数据块, 然后就可以对应的获取到这个描述数据块对应的空闲缓存页, 我们看下图所示.
接着我们就可以把磁盘上的数据页读取到对应的缓存页里去, 同时把相关的一些描述数据写入缓存页的描述数据块里去, 比如这个数据页所属的表空间之类的信息, 最后把那个描述数据块从 free 链表里去除就可以了, 如下图所示.
可能有朋友还是疑惑, 这个描述数据块是怎么从 free 链表里移除的呢?
简单, 我给你一段伪代码演示一下.
假设有一个描述数据块 02, 他的上一个节点是描述数据块 01, 下一个节点是描述数据块 03, 那么他在内存中的数据结构如下.
现在假设 block03 被使用了, 要从 free 链表中移除, 那么此时直接就可以把 block02 节点的 free_next 设置为 null 就可以了, block03 就从 free 链表里失去引用关系了, 如下所示.
想必看到这里, 大家就完全明白, 磁盘中的数据页是如何读取到 Buffer Pool 中的缓存页里去的了, 而且这个过程中 free 链表是用来干什么的.
5, 你怎么知道数据页有没有被缓存?
接着我们来看下一个问题: 你怎么知道一个数据页有没有被缓存呢?
我们在执行增删改查的时候, 肯定是先看看这个数据页有没有被缓存, 如果没被缓存就走上面的逻辑, 从 free 链表中找到一个空闲的缓存页, 从磁盘上读取数据页写入缓存页, 写入描述数据, 从 free 链表中移除这个描述数据块.
但是如果数据页已经被缓存了, 那么就会直接使用了.
所以其实 数据库还会有一个哈希表数据结构, 他会用表空间号 + 数据页号, 作为一个 key, 然后缓存页的地址作为 value.
当你要使用一个数据页的时候, 通过 "表空间号 + 数据页号" 作为 key 去这个哈希表里查一下, 如果没有就读取数据页, 如果已经有了, 就说明数据页已经被缓存了.
我们看下图, 又引入了一个数据页缓存哈希表的结构.
也就是说, 每次你读取一个数据页到缓存之后, 都会在这个哈希表中写入一个 key-value 对, key 就是表空间号 + 数据页号, value 就是缓存页的地址, 那么下次如果你再使用这个数据页, 就可以从哈希表里直接读取出来他已经被放入一个缓存页了.
6, 今日思考题
今天我们给大家留一个思考题, 大家去想一个问题, 我们要取一个数据的时候, 必然会取他所属的一个数据页, 而且这个数据必然是属于一个表的, 所以我们在上面初步引入了一个表空间的概念
也就是说我们写 SQL 的时候, 只知道表 + 行的概念, 但是在 MySQL 内部操作的时候, 是表空间 + 数据页的概念.
那么大家觉得这两者之间的区别是什么? 他们之间的联系是什么?
请大家积极在评论区写下你的思考, 多跟其他同学在评论区中交流.
End
狸猫技术窝专栏 《 从零开始带你成为消息中间件实战高手 》 重磅升级
新增专题 [RocketMQ 源码设计分析] 将用 30 讲内容, 深度剖析 RocketMQ 底层源码, 帮助大家从根上彻底掌握 RocketMQ 这个消息中间件.
新增部分大纲如下:
扫描下方二维码, 了解详细专栏信息:
来源: http://www.tuicool.com/articles/zUfEFvz