资讯动态

mysql原理--InnoDB的Buffer Pool

发布时间:2026/8/15 14:28:56 来源:尧图企业网站定制
1.缓存的重要性对于使用InnoDB作为存储引擎的表来说不管是用于存储用户数据的索引包括聚簇索引和二级索引还是各种系统数据都是以 页 的形式存放在 表空间 中的而所谓的 表空间 只不过是InnoDB对文件系统上一个或几个实际文件的抽象也就是说我们的数据说到底还是存储在磁盘上的。InnoDB存储引擎在处理客户端的请求时当需要访问某个页的数据时就会把完整的页的数据全部加载到内存中也就是说即使我们只需要访问一个页的一条记录那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以进行读写访问了在进行完读写访问之后并不着急把该页对应的内存空间释放掉而是将其 缓存 起来这样将来有请求再次访问该页面时就可以省去磁盘IO的开销了。2.InnoDB的Buffer Pool2.1.啥是个Buffer Pool设计InnoDB的大叔为了缓存磁盘中的页在MySQL服务器启动的时候就向操作系统申请了一片连续的内存他们给这片内存起了个名叫做Buffer Pool中文名是 缓冲池 。默认情况下Buffer Pool只有128M大小。当然如果你嫌弃这个128M太大或者太小可以在启动服务器的时候配置innodb_buffer_pool_size参数的值它表示Buffer Pool的大小就像这样[server]innodb_buffer_pool_size268435456其中268435456的单位是字节也就是我指定Buffer Pool的大小为256M。需要注意的是Buffer Pool也不能太小最小值为5M(当小于该值时会自动设置成5M)。2.2.Buffer Pool内部组成Buffer Pool中默认的缓存页大小和在磁盘上默认的页大小是一样的都是16KB。为了更好的管理这些在Buffer Pool中的缓存页设计InnoDB的大叔为每一个缓存页都创建了一些所谓的 控制信息 这些控制信息包括该页所属的表空间编号、页号、缓存页在Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息锁和 LSN 我们之后会具体唠叨现在可以先忽略当然还有一些别的控制信息我们这就不全唠叨一遍了挑重要的说嘛每个缓存页对应的控制信息占用的内存大小是相同的我们就把每个页对应的控制信息占用的一块内存称为一个控制块 吧控制块和缓存页是一一对应的它们都被存放到Buffer Pool中其中控制块被存放到Buffer Pool的前边缓存页被存放到Buffer Pool后边所以整个Buffer Pool对应的内存空间看起来就是这样的控制块和缓存页之间的那个 碎片 是个什么玩意儿你想想啊每一个控制块都对应一个缓存页那在分配足够多的控制块和缓存页后可能剩余的那点儿空间不够一对控制块和缓存页的大小自然就用不到喽这个用不到的那点儿内存空间就被称为 碎片 了。当然如果你把Buffer Pool的大小设置的刚刚好的话也可能不会产生 碎片 每个控制块大约占用缓存页大小的5%在MySQL5.7.21这个版本中每个控制块占用的大小是808字节。我们设置的innodb_buffer_pool_size并不包含这部分控制块占用的内存空间大小也就是说InnoDB在为Buffer Pool向操作系统申请连续的内存空间时这片连续的内存空间一般会比innodb_buffer_pool_size的值大5%左右。关于有了操作系统的页缓存机制后依然在用户态新增Buffer Pool机制的原因(1). 系统页缓存是面向所有应用提供的通用机制无法感知数据库业务的特点进行针对性优化设计。数据库业务设计高频I/O有必要定制专用缓存实现进行针对性优化。2.3.free链表的管理当我们最初启动MySQL服务器的时候需要完成对Buffer Pool的初始化过程就是先向操作系统申请Buffer Pool的内存空间然后把它划分成若干对控制块和缓存页。但是此时并没有真实的磁盘页被缓存到Buffer Pool中因为还没有用到之后随着程序的运行会不断的有磁盘上的页被缓存到Buffer Pool中。那么从磁盘上读取一个页到Buffer Pool中的时候该放到哪个缓存页的位置呢或者说怎么区分Buffer Pool中哪些缓存页是空闲的哪些已经被使用了呢我们最好在某个地方记录一下Buffer Pool中哪些缓存页是可用的这个时候缓存页对应的 控制块 就派上大用场了我们可以把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中这个链表也可以被称作free链表 或者说空闲链表。刚刚完成初始化的Buffer Pool中所有的缓存页都是空闲的所以每一个缓存页对应的控制块都会被加入到free链表 中假设该Buffer Pool中可容纳的缓存页数量为n那增加了free链表 的效果图就是这样的从图中可以看出我们为了管理好这个free链表 特意为这个链表定义了一个 基节点 里边儿包含着链表的头节点地址尾节点地址以及当前链表中节点的数量等信息。这里需要注意的是链表的基节点占用的内存空间并不包含在为Buffer Pool申请的一大片连续内存空间之内而是单独申请的一块内存空间。链表基节点占用的内存空间并不大在MySQL5.7.21这个版本里每个基节点只占用40字节大小。后边我们即将介绍许多不同的链表它们的基节点和free链表的基节点的内存分配方式是一样一样的都是单独申请的一块40字节大小的内存空间并不包含在为Buffer Pool申请的一大片连续内存空间之内。有了这个free链表 之后事儿就好办了每当需要从磁盘中加载一个页到Buffer Pool中时就从free链表 中取一个空闲的缓存页并且把该缓存页对应的 控制块 的信息填上就是该页所在的表空间、页号之类的信息然后把该缓存页对应的free链表 节点从链表中移除表示该缓存页已经被使用了2.4.缓存页的哈希处理我们前边说过当我们需要访问某个页中的数据时就会把该页从磁盘加载到Buffer Pool中如果该页已经在Buffer Pool中的话直接使用就可以了。那么问题也就来了我们怎么知道该页在不在Buffer Pool中呢我们其实是根据表空间号 页号来定位一个页的也就相当于表空间号 页号是一个key缓存页就是对应的value怎么通过一个key来快速找着一个value呢哈哈那肯定是哈希表喽所以我们可以用表空间号 页号作为key缓存页作为value创建一个哈希表在需要访问某个页的数据时先从哈希表中根据表空间号 页号看看有没有对应的缓存页如果有直接使用该缓存页就好如果没有那就从free链表 中选一个空闲的缓存页然后把磁盘中对应的页加载到该缓存页的位置。2.5.flush链表的管理如果我们修改了Buffer Pool中某个缓存页的数据那它就和磁盘上的页不一致了这样的缓存页也被称为 脏页 英文名dirty page。当然最简单的做法就是每发生一次修改就立即同步到磁盘上对应的页上但是频繁的往磁盘中写数据会严重的影响程序的性能毕竟磁盘慢的像乌龟一样。所以每次修改缓存页后我们并不着急立即把修改同步到磁盘上而是在未来的某个时间点进行同步至于这个同步的时间点我们后边会作说明说明的现在先不用管哈但是如果不立即同步到磁盘的话那之后再同步的时候我们怎么知道Buffer Pool中哪些页是 脏页 哪些页从来没被修改过呢总不能把所有的缓存页都同步到磁盘上吧假如Buffer Pool被设置的很大比方说300G那一次性同步这么多数据岂不是要慢死所以我们不得不再创建一个存储脏页的链表凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的所以也叫flush链表 。链表的构造和free链表 差不多假设某个时间点Buffer Pool中的脏页数量为n那么对应的flush链表 就长这样2.6.LRU链表的管理2.6.1.缓存不够的窘境Buffer Pool对应的内存大小毕竟是有限的如果需要缓存的页占用的内存大小超过了Buffer Pool大小也就是free链表 中已经没有多余的空闲缓存页的时候岂不是很尴尬发生了这样的事儿该咋办当然是把某些旧的缓存页从Buffer Pool中移除然后再把新的页放进来喽 那么问题来了移除哪些缓存页呢为了回答这个问题我们还需要回到我们设立Buffer Pool的初衷我们就是想减少和磁盘的IO交互最好每次在访问某个页的时候它都已经被缓存到Buffer Pool中了。假设我们一共访问了n次页那么被访问的页已经在缓存中的次数除以n就是所谓的 缓存命中率 我们的期望就是让 缓存命中率 越高越好 从这个角度出发回想一下我们的微信聊天列表排在前边的都是最近很频繁使用的排在后边的自然就是最近很少使用的假如列表能容纳下的联系人有限你是会把最近很频繁使用的留下还是最近很少使用的留下呢废话当然是留下最近很频繁使用的了2.6.2.简单的LRU链表管理Buffer Pool的缓存页其实也是这个道理当Buffer Pool中不再有空闲的缓存页时就需要淘汰掉部分最近很少使用的缓存页。不过我们怎么知道哪些缓存页最近频繁使用哪些最近很少使用呢我们可以再创建一个链表由于这个链表是为了 按照最近最少使用 的原则去淘汰缓存页的所以这个链表可以被称为LRU链表 LRU的英文全称Least Recently Used。当我们需要访问某个页时可以这样处理LRU链表 (1). 如果该页不在Buffer Pool中在把该页从磁盘加载到Buffer Pool中的缓存页时就把该缓存页对应的控制块 作为节点塞到链表的头部。(2). 如果该页已经缓存在Buffer Pool中则直接把该页对应的 控制块 移动到LRU链表 的头部。也就是说只要我们使用到某个缓存页就把该缓存页调整到LRU链表 的头部这样LRU链表 尾部就是最近最少使用的缓存页喽 所以当Buffer Pool中的空闲缓存页使用完时到LRU链表 的尾部找些缓存页淘汰就OK啦真简单啧啧…2.6.2.划分区域的LRU链表上边的这个简单的LRU链表 用了没多长时间就发现问题了因为存在这两种比较尴尬的情况(1).InnoDB提供了一个看起来比较贴心的服务—— 预读 英文名read ahead。所谓 预读 就是InnoDB认为执行当前的请求可能之后会读取某些页面就预先把它们加载到Buffer Pool中。根据触发方式的不同 预读 又可以细分为下边两种a. 线性预读设计 InnoDB 的大叔提供了一个系统变量innodb_read_ahead_threshold如果顺序访问了某个区extent的页面超过这个系统变量的值就会触发一次 异步 读取下一个区中全部的页面到Buffer Pool的请求注意 异步 读取意味着从磁盘中加载这些被预读的页面并不会影响到当前工作线程的正常执行。这个innodb_read_ahead_threshold系统变量的值默认是56我们可以在服务器启动时通过启动参数或者服务器运行过程中直接调整该系统变量的值不过它是一个全局变量注意使用SET GLOBAL命令来修改哦。b.随机预读如果Buffer Pool中已经缓存了某个区的13个连续的页面不论这些页面是不是顺序读取的都会触发一次 异步 读取本区中所有其的页面到Buffer Pool的请求。设计InnoDB的大叔同时提供了innodb_random_read_ahead系统变量它的默认值为OFF也就意味着InnoDB并不会默认开启随机预读的功能如果我们想开启该功能可以通过修改启动参数或者直接使用SET GLOBAL命令把该变量的值设置为ON。预读 本来是个好事儿如果预读到Buffer Pool中的页成功的被使用到那就可以极大的提高语句执行的效率。可是如果用不到呢这些预读的页都会放到LRU链表的头部但是如果此时Buffer Pool的容量不太大而且很多预读的页面都没有用到的话这就会导致处在LRU链表 尾部的一些缓存页会很快的被淘汰掉也就是所谓的劣币驱逐良币 会大大降低缓存命中率。(2). 有的小伙伴可能会写一些需要扫描全表的查询语句比如没有建立合适的索引或者压根儿没有WHERE子句的查询。扫描全表意味着什么意味着将访问到该表所在的所有页假设这个表中记录非常多的话那该表会占用特别多的 页 当需要访问这些页时会把它们统统都加载到Buffer Pool中这也就意味着吧唧一下Buffer Pool中的所有页都被换了一次血其他查询语句在执行时又得执行一次从磁盘加载到Buffer Pool的操作。而这种全表扫描的语句执行的频率也不高每次执行都要把Buffer Pool中的缓存页换一次血这严重的影响到其他查询对Buffer Pool的使用从而大大降低了缓存命中率。总结一下上边说的可能降低 Buffer Pool 的两种情况(1). 加载到Buffer Pool中的页不一定被用到。(2). 如果非常多的使用频率偏低的页被同时加载到Buffer Pool时可能会把那些使用频率非常高的页从Buffer Pool中淘汰掉。因为有这两种情况的存在所以设计InnoDB的大叔把这个LRU链表 按照一定比例分成两截分别是(1). 一部分存储使用频率非常高的缓存页所以这一部分链表也叫做 热数据 或者称young区域 。(2). 另一部分存储使用频率不是很高的缓存页所以这一部分链表也叫做 冷数据 或者称old区域 。为了方便大家理解我们把示意图做了简化各位领会精神就好大家要特别注意一个事儿我们是按照某个比例将LRU链表分成两半的不是某些节点固定是young区域的某些节点固定是old区域的随着程序的运行某个节点所属的区域也可能发生变化。那这个划分成两截的比例怎么确定呢对于InnoDB存储引擎来说我们可以通过查看系统变量innodb_old_blocks_pct的值来确定old区域在LRU链表 中所占的比例比方说这样从结果可以看出来默认情况下old区域在 LRU链表 中所占的比例是37%也就是说old区域大约占LRU链表 的3/8。这个比例我们是可以设置的我们可以在启动时修改innodb_old_blocks_pct参数来控制old区域在LRU链表 中所占的比例比方说这样修改配置文件[server]innodb_old_blocks_pct40这样我们在启动服务器后old区域占LRU链表 的比例就是40%。当然如果在服务器运行期间我们也可以修改这个系统变量的值不过需要注意的是这个系统变量属于 全局变量 一经修改会对所有客户端生效所以我们只能这样修改SET GLOBAL innodb_old_blocks_pct 40;有了这个被划分成young和old区域的LRU链表之后设计InnoDB的大叔就可以针对我们上边提到的两种可能降低缓存命中率的情况进行优化了(1). 针对预读的页面可能不进行后续访情况的优化设计InnoDB的大叔规定当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时该缓存页对应的控制块会被放到old区域的头部。这样针对预读到Buffer Pool却不进行后续访问的页面就会被逐渐从old区域逐出而不会影响young区域中被使用比较频繁的缓存页。(2). 针对全表扫描时短时间内访问大量使用频率非常低的页面情况的优化在进行全表扫描时虽然首次被加载到Buffer Pool的页被放到了old区域的头部但是后续会被马上访问到每次进行访问的时候又会把该页放到young区域的头部这样仍然会把那些使用频率比较高的页面给顶下去。有同学会想可不可以在第一次访问该页面时不将其从old区域移动到young区域的头部后续访问时再将其移动到young区域的头部。回答是行不通因为设计InnoDB的大叔规定每次去页面中读取一条记录时都算是访问一次页面而一个页面中可能会包含很多条记录也就是说读取完某个页面的记录就相当于访问了这个页面好多次。咋办全表扫描有一个特点那就是它的执行频率非常低谁也不会没事儿老在那写全表扫描的语句玩而且在执行全表扫描的过程中即使某个页面中有很多条记录也就是去多次访问这个页面所花费的时间也是非常少的。所以我们只需要规定在对某个处在old区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间如果后续的访问时间与第一次访问的时间在某个时间间隔内那么该页面就不会被从old区域移动到young区域的头部否则将它移动到young区域的头部。这个间隔时间是由系统变量innodb_old_blocks_time控制的你看这个innodb_old_blocks_time的默认值是1000它的单位是毫秒也就意味着对于从磁盘上被加载到LRU链表的old区域的某个页来说如果第一次和最后一次访问该页面的时间间隔小于1s很明显在一次全表扫描的过程中多次访问一个页面中的时间不会超过1s那么该页是不会被加入到young区域的当然像innodb_old_blocks_pct一样我们也可以在服务器启动或运行时设置innodb_old_blocks_time的值这里就不赘述了你自己试试吧 这里需要注意的是如果我们把innodb_old_blocks_time的值设置为0那么每次我们访问一个页面时就会把该页面放到young区域的头部。综上所述正是因为将LRU链表划分为young和old区域这两个部分又添加了innodb_old_blocks_time这个系统变量才使得预读机制和全表扫描造成的缓存命中率降低的问题得到了遏制因为用不到的预读页面以及全表扫描的页面都只会被放到old区域而不影响young区域中的缓存页。2.6.3.更进一步优化LRU链表对于young区域的缓存页来说我们每次访问一个缓存页就要把它移动到LRU链表 的头部这样开销是不是太大啦毕竟在young区域的缓存页都是热点数据也就是可能被经常访问的这样频繁的对LRU链表 进行节点移动操作是不是不太好啊是的为了解决这个问题其实我们还可以提出一些优化策略比如只有被访问的缓存页位于young区域的1/4的后边才会被移动到LRU链表 头部这样就可以降低调整LRU链表 的频率从而提升性能也就是说如果某个缓存页对应的节点在young区域的1/4中再次访问该缓存页时也不会将其移动到LRU链表头部。我们之前介绍随机预读的时候曾说如果Buffer Pool中有某个区的13个连续页面就会触发随机预读这其实是不严谨的不幸的是MySQL文档就是这么说的[摊手]其实还要求这13个页面是非常热的页面所谓的非常热指的是这些页面在整个young区域的头1/4处。但是不论怎么优化千万别忘了我们的初心尽量高效的提高Buffer Pool的缓存命中率。2.7.其他的一些链表为了更好的管理Buffer Pool中的缓存页除了我们上边提到的一些措施设计InnoDB的大叔们还引进了其他的一些 链表 比如unzipLRU链表 用于管理解压页zip clean链表 用于管理没有被解压的压缩页zip free数组 中每一个元素都代表一个链表它们组成所谓的 伙伴系统 来为压缩页提供内存空间等等反正是为了更好的管理这个Buffer Pool引入了各种链表或其他数据结构具体的使用方式就不啰嗦了大家有兴趣深究的再去找些更深的书或者直接看源代码吧。2.8.刷新脏页到磁盘后台有专门的线程每隔一段时间负责把脏页刷新到磁盘这样可以不影响用户线程处理正常的请求。主要有两种刷新路径(1). 从LRU链表 的冷数据中刷新一部分页面到磁盘。后台线程会定时从LRU链表 尾部开始扫描一些页面扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定如果从里边儿发现脏页会把它们刷新到磁盘。这种刷新页面的方式被称之为BUF_FLUSH_LRU。(2). 从flush链表 中刷新一部分页面到磁盘。后台线程也会定时从flush链表 中刷新一部分页面到磁盘刷新的速率取决于当时系统是不是很繁忙。这种刷新页面的方式被称之为BUF_FLUSH_LIST。有时候后台线程刷新脏页的进度比较慢导致用户线程在准备加载一个磁盘页到Buffer Pool时没有可用的缓存页这时就会尝试看看LRU链表 尾部有没有可以直接释放掉的未修改页面如果没有的话会不得不将LRU链表 尾部的一个脏页同步刷新到磁盘和磁盘交互是很慢的这会降低处理用户请求的速度。这种刷新单个页面到磁盘中的刷新方式被称之为BUF_FLUSH_SINGLE_PAGE。当然有时候系统特别繁忙时也可能出现用户线程批量的从flush链表 中刷新脏页的情况很显然在处理用户请求过程中去刷新脏页是一种严重降低处理速度的行为毕竟磁盘的速度慢的要死这属于一种迫不得已的情况不过这得放在后边唠叨redo日志的checkpoint时说了。3.多个Buffer Pool实例我们上边说过Buffer Pool本质是InnoDB向操作系统申请的一块连续的内存空间在多线程环境下访问Buffer Pool中的各种链表都需要加锁处理啥的在Buffer Pool特别大而且多线程并发访问特别高的情况下单一的Buffer Pool可能会影响请求的处理速度。所以在Buffer Pool特别大的时候我们可以把它们拆分成若干个小的Buffer Pool每个Buffer Pool都称为一个 实例 它们都是独立的独立的去申请内存空间独立的管理各种链表独立的吧啦吧啦所以在多线程并发访问时并不会相互影响从而提高并发处理能力。我们可以在服务器启动的时候通过设置innodb_buffer_pool_instances的值来修改Buffer Pool实例的个数比方说这样[server]innodb_buffer_pool_instances2这样就表明我们要创建2个 Buffer Pool 实例示意图就是这样那每个Buffer Pool实例实际占多少内存空间呢其实使用这个公式算出来的innodb_buffer_pool_size/innodb_buffer_pool_instances。也就是总共的大小除以实例的个数结果就是每个Buffer Pool实例占用的大小。不过也不是说Buffer Pool实例创建的越多越好分别管理各个Buffer Pool也是需要性能开销的设计InnoDB的大叔们规定当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的InnoDB会默认把innodb_buffer_pool_instances的值修改为1。而我们鼓励在Buffer Pool大小或等于1G的时候设置多个Buffer Pool实例。4.innodb_buffer_pool_chunk_size在MySQL 5.7.5之前Buffer Pool的大小只能在服务器启动时通过配置innodb_buffer_pool_size启动参数来调整大小在服务器运行过程中是不允许调整该值的。不过设计MySQL的大叔在5.7.5以及之后的版本中支持了在服务器运行过程中调整Buffer Pool大小的功能但是有一个问题就是每次当我们要重新调整Buffer Pool大小时都需要重新向操作系统申请一块连续的内存空间然后将旧的Buffer Pool中的内容复制到这一块新空间这是极其耗时的。所以设计MySQL的大叔们决定不再一次性为某个Buffer Pool实例向操作系统申请一大片连续的内存空间而是以一个所谓的chunk为单位向操作系统申请空间。也就是说一个Buffer Pool实例其实是由若干个chunk组成的一个chunk就代表一片连续的内存空间里边儿包含了若干缓存页与其对应的控制块画个图表示就是这样上图代表的Buffer Pool就是由2个实例组成的每个实例中又包含2个chunk。正是因为发明了这个chunk的概念我们在服务器运行期间调整Buffer Pool的大小时就是以chunk为单位增加或者删除内存空间而不需要重新向操作系统申请一片大的内存然后进行缓存页的复制。这个所谓的chunk的大小是我们在启动操作MySQL服务器时通过innodb_buffer_pool_chunk_size启动参数指定的它的默认值是134217728也就是128M。注意的是innodb_buffer_pool_chunk_size的值只能在服务器启动时指定在服务器运行过程中是不可以修改的。这个innodb_buffer_pool_chunk_size的值并不包含缓存页对应的控制块的内存空间大小所以实际上InnoDB向操作系统申请连续内存空间时每个chunk的大小要比innodb_buffer_pool_chunk_size的值大一些约5%。5.配置Buffer Pool时的注意事项(1).innodb_buffer_pool_size必须是innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances的倍数。假设我们指定的innodb_buffer_pool_chunk_size的值是128Minnodb_buffer_pool_instances的值是16那么这两个值的乘积就是2G也就是说innodb_buffer_pool_size的值必须是2G或者2G的整数倍。比方说我们在启动MySQL服务器是这样指定启动参数的mysqld --innodb-buffer-pool-size8G --innodb-buffer-pool-instances16。默认的innodb_buffer_pool_chunk_size值是128M指定的innodb_buffer_pool_instances的值是16所以innodb_buffer_pool_size的值必须是2G或者2G的整数倍上边例子中指定的innodb_buffer_pool_size的值是8G符合规定所以在服务器启动完成之后我们查看一下该变量的值就是我们指定的8G8589934592字节。如果我们指定的innodb_buffer_pool_size大于2G并且不是2G的整数倍那么服务器会自动的把innodb_buffer_pool_size的值调整为2G的整数倍。(2). 如果在服务器启动时innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances的值已经大于innodb_buffer_pool_size的值那么innodb_buffer_pool_chunk_size的值会被服务器自动设置为innodb_buffer_pool_size/innodb_buffer_pool_instances的值。比方说我们在启动服务器时指定的innodb_buffer_pool_size的值为2Ginnodb_buffer_pool_instances的值为16innodb_buffer_pool_chunk_size的值为256M。innodb_buffer_pool_chunk_size值会被服务器改写为innodb_buffer_pool_size/innodb_buffer_pool_instances的值也就是2G/16 128M134217728字节6.Buffer Pool中存储的其它信息Buffer Pool的缓存页除了用来缓存磁盘上的页面以外还可以存储锁信息、自适应哈希索引等信息这些内容等我们之后遇到了再详细讨论哈7.查看Buffer Pool的状态信息设计 MySQL 的大叔贴心的给我们提供了SHOW ENGINE INNODB STATUS语句来查看关于 InnoDB 存储引擎运行过程中的一些状态信息其中就包括Buffer Pool的一些信息我们看一下为了突出重点我们只把输出中关于Buffer Pool的部分提取了出来我们来详细看一下这里边的每个值都代表什么意思(1).Total memory allocated代表Buffer Pool向操作系统申请的连续内存空间大小包括全部控制块、缓存页、以及碎片的大小。(2).Dictionary memory allocated为数据字典信息分配的内存空间大小注意这个内存空间和Buffer Pool没啥关系不包括在Total memory allocated中。(3).Buffer pool size代表该Buffer Pool可以容纳多少缓存 页 注意单位是 页 (4).Free buffers代表当前Buffer Pool还有多少空闲缓存页也就是free链表 中还有多少个节点。(5).Database pages代表LRU链表中的页的数量包含young和old两个区域的节点数量。(6).Old database pages代表LRU链表old区域的节点数量。(7).Modified db pages代表脏页数量也就是flush链表 中节点的数量。(8).Pending reads正在等待从磁盘上加载到Buffer Pool中的页面数量。当准备从磁盘中加载某个页面时会先为这个页面在Buffer Pool中分配一个缓存页以及它对应的控制块然后把这个控制块添加到LRU的old区域的头部但是这个时候真正的磁盘页并没有被加载进来Pending reads的值会跟着加1。(9).Pending writes LRU即将从LRU链表中刷新到磁盘中的页面数量。(10).Pending writes flush list即将从flush链表中刷新到磁盘中的页面数量。(11).Pending writes single page即将以单个页面的形式刷新到磁盘中的页面数量。(12).Pages made young代表LRU链表中曾经从old区域移动到young区域头部的节点数量。这里需要注意一个节点每次只有从old区域移动到young区域头部时才会将Pages made young的值加1也就是说如果该节点本来就在young区域由于它符合在young区域1/4后边的要求下一次访问这个页面时也会将它移动到young区域头部但这个过程并不会导致Pages made young的值加1。(13).Page made not young在将innodb_old_blocks_time设置的值大于0时首次访问或者后续访问某个处在old区域的节点时由于不符合时间间隔的限制而不能将其移动到young区域头部时Page made not young的值会加1。这里需要注意对于处在young区域的节点如果由于它在young区域的1/4处而导致它没有被移动到young区域头部这样的访问并不会将Page made not young的值加1。(14).youngs/s代表每秒从old区域被移动到young区域头部的节点数量。(15).non-youngs/s代表每秒由于不满足时间限制而不能从old区域移动到young区域头部的节点数量。(16).Pages read 、 created 、 written代表读取创建写入了多少页。后边跟着读取、创建、写入的速率。(17).Buffer pool hit rate表示在过去某段时间平均访问1000次页面有多少次该页面已经被缓存到Buffer Pool了。(18).young-making rate表示在过去某段时间平均访问1000次页面有多少次访问使页面移动到young区域的头部了。需要大家注意的一点是这里统计的将页面移动到young区域的头部次数不仅仅包含从old区域移动到young区域头部的次数还包括从young区域移动到young区域头部的次数访问某个young区域的节点只要该节点在young区域的1/4处往后就会把它移动到young区域的头部。(19).not (young-making rate)表示在过去某段时间平均访问1000次页面有多少次访问没有使页面移动到young区域的头部。需要大家注意的一点是这里统计的没有将页面移动到young区域的头部次数不仅仅包含因为设置了innodb_old_blocks_time系统变量而导致访问了old区域中的节点但没把它们移动到young区域的次数还包含因为该节点在young区域的前1/4处而没有被移动到young区域头部的次数。(20).LRU len代表LRU链表 中节点的数量。(21).unzip_LRU代表unzip_LRU链表 中节点的数量由于我们没有具体唠叨过这个链表现在可以忽略它的值。(22).I/O sum最近50s读取磁盘页的总数。(23).I/O cur现在正在读取的磁盘页数量。(24).I/O unzip sum最近50s解压的页面数量。(25).I/O unzip cur正在解压的页面数量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价