MySQL系列3:缓冲池Buffer Pool的设计思想
1. 回顾
上一篇我们主要讲了InnoDB的存储引擎,其中主要的一个组件就是缓存池Buffer Pool,缓存了磁盘的真实数据,然后基于缓存做增删改查操作,同时配合了后续的redo log、刷磁盘等机制和操作。如下图:

这一篇,深入该组件内部,学习一下其设计思想。
2. Buffer Pool数据结构
Buffer Pool本质其实就是数据库的一个内存组件,默认情况下是128MB,如果我们的数据库如果是16核32G的机器,那么你就可以给Buffer Pool分配个2GB的内存,使用下面的配置就可以了
innodb_buffer_pool_size = 2147483648
磁盘加载数据页到缓存,数据页在缓存中被定义为缓存页,缓存页与缓存页默认16KB,每个缓存页有对应的描述数据,描述了这个数据页所属的表空间、数据页的编号、这个缓存页在Buffer Pool中的地址等。在Buffer Pool中,每个缓存页的描述数据放在最前面,各个缓存页放在后面。Buffer Pool中的描述数据大概相当于缓存页大小的5%左右,也就是每个描述数据大概是800个字节左右的大小,然后假设你设置的buffer pool大小是128MB,实际上Buffer Pool真正的最终大小会超出一些,可能有个130多MB的样子,因为他里面还要存放每个缓存页的描述数据。
数据结构如下图:

3. free链表
接着我们来看下一个问题,当数据库运行起来之后,肯定会不停的执行增删改查的操作,此时就需要不停的从磁盘上读取一个一个的数据页放入Buffer Pool中的对应的缓存页里去,把数据缓存起来,那么以后就可以对这个数据在内存里执行增删改查了。但是此时在从磁盘上读取数据页放入Buffer Pool中的缓存页的时候,必然涉及到一个问题,就是哪些缓存页是空闲的?
所以数据库会为Buffer Pool设计一个free链表,他是一个双向链表数据结构,这个free链表里,每个节点就是一个空闲的缓存页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么他的描述数据块就会被放入这个free链表中。如果要把数据页写入缓存页,就从链表中摘除这个节点,将该节点的描述数据写到Buffer pool,再把写入对应的缓存页。
写入之前还会判断该数据页是否已经被缓存,引入哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value,当要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果已经有了,就说明数据页已经被缓存了。
如下图所示:

4. flush链表
更新过的缓存页与磁盘不一致,需要刷到磁盘的缓冲页构成的双向链表;也叫待刷盘的脏页数据页链表;如下图所示

5. lru链表
如果所有的缓存页都被塞了数据了,此时无法从磁盘上加载新的数据页到缓存页里去了,那么此时你只有一个办法,就是淘汰掉一些缓存页。引入LRU链表来判断哪些缓存页是不常用的。这个所谓的LRU就是Least Recently Used,最近最少使用的意思。
假设某个缓存页的描述数据块本来在LRU链表的尾部,后续你只要查询或者修改了这个缓存页的数据,也要把这个缓存页挪动到LRU链表的头部去,也就是说最近被访问过的缓存页,一定在LRU链表的头部。如下图所示

淘汰不常用的缓存页,尾部淘汰冷数据,头部插入热数据。
6. 数据页预读带来的问题
MySQL为提升读取性能,引入了预读机制,就是当从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页,也加载到缓存里去!
举个例子,假设现在有两个空闲缓存页,然后在加载一个数据页的时候,连带着把他的一个相邻的数据页也加载到缓存里去了,正好每个数据页放入一个空闲缓存页!但是接下来呢,实际上只有一个缓存页是被访问了,另外一个通过预读机制加载的缓存页,其实并没有人访问,此时这两个缓存页可都在LRU链表的前面。

我们可以看到,这个图里很清晰的表明了,前两个缓存页都是刚加载进来的,但是此时第二个缓存页是通过预读机制捎带着加载进来的,他也放到了链表的前面,但是他实际没人访问他。除了第二个缓存页之外,第一个缓存页,以及尾巴上两个缓存页,都是一直有人访问的那种缓存页,只不过上图代表的是刚刚把头部两个缓存页加载进来的时候的一个LRU链表当时的情况。
哪些场景会导致预读呢?
(1)有一个参数是innodb_read_ahead_threshold,他的默认值是56,意思就是如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区中的所有数据页都加载到缓存里去。
(2)如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去。
这个机制是通过参数innodb_random_read_ahead来控制的,他默认是OFF,也就是这个规则是关闭的。
(3)接着我们讲另外一种可能导致频繁被访问的缓存页被淘汰的场景,那就是全表扫描。
这个所谓的全表扫描,意思就是类似如下的SQL语句:SELECT * FROM USERS,此时他没加任何一个where条件,会导致他直接一下子把这个表里所有的数据页,都从磁盘加载到Buffer Pool里去。这个时候他可能会一下子就把这个表的所有数据页都一一装入各个缓存页里去!此时可能LRU链表中排在前面的一大串缓存页,都是全表扫描加载进来的缓存页!那么如果这次全表扫描过后,后续几乎没用到这个表里的数据呢?此时LRU链表的尾部,可能全部都是之前一直被频繁访问的那些缓存页!然后当你要淘汰掉一些缓存页腾出空间的时候,就会把LRU链表尾部一直被频繁访问的缓存页给淘汰掉了,而留下了之前全表扫描加载进来的大量的不经常访问的缓存页。
7. 解决预读带来的问题
所以为了解决上一讲我们说的简单的LRU链表的问题,真正MySQL在设计LRU链表的时候,采取的实际上是冷热数据分离的思想。
所以真正的LRU链表,会被拆分为两个部分,一部分是热数据,一部分是冷数据,这个冷热数据的比例是由innodb_old_blocks_pct参数控制的,他默认是37,也就是说冷数据占比37%。

第一次被加载了数据的缓存页,都会不停的移动到冷数据区域的链表头部。冷数据区域的缓存页什么时候会放到热数据区域呢?实际上肯定很多人会想,只要对冷数据区域的缓存页进行了一次访问,就立马把这个缓存页放到热数据区域的头部行不行呢?
其实这也是不合理的,如果你刚加载了一个数据页到那个缓存页,他是在冷数据区域的链表头部,然后立马(在1ms以内)就访问了一下这个缓存页,之后就再也不访问他了呢?难道这种情况你也要把那个缓存页放到热数据区域的头部吗?
所以MySQL设定了一个规则,他设计了一个innodb_old_blocks_time参数,默认值1000,也就是1000毫秒。也就是说,必须是一个数据页被加载到缓存页之后,在1s之后,你访问这个缓存页,他才会被挪动到热数据区域的链表头部去。因为假设你加载了一个数据页到缓存去,然后过了1s之后你还访问了这个缓存页,说明你后续很可能会经常要访问它,这个时间限制就是1s,因此只有1s后你访问了这个缓存页,他才会给你把缓存页放到热数据区域的链表头部去。

该思想通过冷热分离+时间访问限制,解决了误淘汰热数据的问题。吸收冷热隔离思想,结合项目场景,可以优化缓存中的冷热数据。
LRU链表的热数据区域是如何进行优化的呢?
热数据区域里的缓存页可能是经常被访问的,所以这么频繁的进行移动是不是性能也并不是太好?也没这个必要。
所以说,LRU链表的热数据区域的访问规则被优化了一下,即你只有在热数据区域的后3/4部分的缓存页被访问了,才会给你移动到链表头部去。如果你是热数据区域的前面1/4的缓存页被访问,他是不会移动到链表头部去的。举个例子,假设热数据区域的链表里有100个缓存页,那么排在前面的25个缓存页,他即使被访问了,也不会移动到链表头部去的。但是对于排在后面的75个缓存页,他只要被访问,就会移动到链表头部去。这样的话,他就可以尽可能的减少链表中的节点移动了。
8. 总结
本节主要讲了三链表的作用,free链表记录空闲缓存页,flush链表记录脏页,即待刷盘缓存页,当free链表没有空闲时,lru链表淘汰最近不常用的缓存页。三链表动态执行过程可以表述为:free链表移除结点,lru链表冷数据区头部加入该节点;如果修改了缓存页,flush加入这个脏页,lru表中还可能会从冷数据区域移动到热数据区域的头部去;如果查询了缓存页,会把这个缓存页在lru链表中移动到热数据区域去,或者在热数据区域中也有可能会移动到头部去。总之,要么free链表移除节点,flush链表加节点,lru链表移动节点;要么free加节点,flush减节点,lru减节点。
MySQL系列3:缓冲池Buffer Pool的设计思想的更多相关文章
- 【大白话系统】MySQL 学习总结 之 缓冲池(Buffer Pool) 的设计原理和管理机制
一.缓冲池(Buffer Pool)的地位 在<MySQL 学习总结 之 InnoDB 存储引擎的架构设计>中,我们就讲到,缓冲池是 InnoDB 存储引擎中最重要的组件.因为为了提高 M ...
- 【大白话系统】MySQL 学习总结 之 缓冲池(Buffer Pool) 如何支撑高并发和动态调整
如果大家对我的 [大白话系列]MySQL 学习总结系列 感兴趣的话,可以点击关注一波. 一.上节回顾 在上节< 缓冲池(Buffer Pool) 的设计原理和管理机制>中,介绍了缓冲池整体 ...
- MySql 缓冲池(buffer pool) 和 写缓存(change buffer) 转
应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库. 操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问. M ...
- MySQL · 性能优化· InnoDB buffer pool flush策略漫谈
MySQL · 性能优化· InnoDB buffer pool flush策略漫谈 背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数 ...
- (1.16)mysql server优化之buffer pool
(1.16)mysql server优化之buffer pool 1.innodb buffer pool 查看 show status like 'Innodb_buffer_pool_%'; 该 ...
- MySQL · 引擎特性 · InnoDB Buffer Pool
前言 用户对数据库的最基本要求就是能高效的读取和存储数据,但是读写数据都涉及到与低速的设备交互,为了弥补两者之间的速度差异,所有数据库都有缓存池,用来管理相应的数据页,提高数据库的效率,当然也因为引入 ...
- InnoDB 中的缓冲池(Buffer Pool)
本文主要说明 InnoDB Buffer Pool 的内部执行原理,其生效的前提是使用到了索引,如果没有用到索引会进行全表扫描. 结构 在 InnoDB 存储引擎层维护着一个缓冲池,通过其可以避免对磁 ...
- 【转载】MySQL · 性能优化· InnoDB buffer pool flush策略漫谈
背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数个内存块加上一组控制结构体对象组成.内存块的个数取决于buffer pool inst ...
- mysql内核源代码深度解析 缓冲池 buffer pool 整体概述
http://blog.csdn.net/cjcl99/article/details/51063078
- MySQL中读页缓冲区buffer pool
Buffer pool 我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理.我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其缓存起来,所以MyS ...
随机推荐
- Vue——表单控制、购物车案例、v-model进阶、与后端交互三种方式、箭头函数
表单控制 // 1 checkbox 单选 多选 // 2 radio 单选 <body> <div id="app"> <h1>checkbo ...
- binfmt_misc
一:binfmt_misc是什么 binfmt_misc是内核中的一个功能,它能将非本机的二进制文件与特定的解析器自动匹配起来,进行二进制解析. 例如,在x86上解析arm64架构的二进制. 通过bi ...
- 【TVM模型编译】1. onnx2relay.md
上一篇介绍了onnx模型在tvm中优化的总体流程. 在这一篇中,介绍onnx模型到relay模型的转换流程,主要涉及了以下几个方面: onnx算子到relay算子转换 relay算子实现 这一篇介绍o ...
- MVCC并发版本控制之重点ReadView
MVCC并发版本控制 本文大部分来自<MySQL是怎样运行的>,这里只是简单总结,用于各位回忆和复习. 版本链 对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的 ...
- 使用 nuxt3 开发简约优雅的个人 blog
起因 很早前我就有过搭建个人博客的想法,但是我希望使用纯前端实现,这样就不需要付出额外的后端维护成本,维护成本又低,而且更加安全.网上也有很多博客框架但是也不符合我的需求,所以我使用了nuxt3 + ...
- celery笔记九之task运行结果查看
本文首发于公众号:Hunter后端 原文链接:celery笔记九之task运行结果查看 这一篇笔记介绍一下 celery 的 task 运行之后结果的查看. 前面我们使用的配置是这样的: # sett ...
- C++ 核心指南之资源管理(下)—— 智能指针最佳实践
C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...
- 【JavaScript】你真的熟悉bind吗
引言 内容速递 看了本文您能了解到的知识! 在本篇文章中,将带你了解什么是bind,bind的用途.如何手写bind以及工作中实际使用bind的场景. 在JavaScript中,bind()方法是用来 ...
- linux内核Makefile中的变量build--- 过渡篇(五)
一. kbuild系统主要涉及的几个文件 文件名 作用 Makefile 内核源代码顶层目录的Makefile文件 scripts/Makefile.build 通常在进行递归make时会用到的Mak ...
- 【后端面经-Java】JVM垃圾回收机制
目录 1. Where:回收哪里的东西?--JVM内存分配 2. Which:内存对象中谁会被回收?--GC分代思想 2.1 年轻代/老年代/永久代 2.2 内存细分 3. When:什么时候回收垃圾 ...