MySQL Buffer Pool

news2024/11/16 5:43:13

总结自:小林coding,bojiangzhou

虽然说 MySQL 的数据是存储在磁盘里的,但是也不能每次都从磁盘里面读取数据,这样性能是极差的。

要想提升查询性能,加个缓存就行了嘛。所以,当数据从磁盘中取出后,缓存内存中,下次查询同样的数据的时候,直接从内存中读取。为此,Innodb 存储引擎设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。

Buffer Pool 是在 MySQL 启动的时候,向操作系统申请的一片连续的内存空间,默认配置下 Buffer Pool 只有 128MB 。然后按照默认的16KB的大小划分出一个个的页, Buffer Pool 中的页就叫做缓存页。

可以通过调整 innodb_buffer_pool_size 参数来设置 Buffer Pool 的大小,一般建议设置成可用物理内存的 60%~80%。

为了更好的管理这些在 Buffer Pool 中的缓存页,InnoDB 为每一个缓存页都创建了一个控制块,控制块信息包括「缓存页的表空间、页号、缓存页地址、链表节点」等等。控制块也是占有内存空间的,它是放在 Buffer Pool 的最前面,接着才是缓存页。

管理Buffer Pool

缓存页哈希表

有些数据页被加载到 Buffer Pool 的缓存页中了,那怎么知道一个数据页有没有被缓存呢?

所以InnoDB还会有一个哈希表数据结构,它用 表空间号+数据页号 作key,value 就是缓存页的地址。

当使用一个数据页的时候,会先通过表空间号+数据页号作为key去这个哈希表里查一下,如果没有就从磁盘读取数据页,如果已经有了,就直接使用该缓存页。

管理空闲页(Free List)

那当我们从磁盘读取数据的时候,总不能通过遍历这一片连续的内存空间来找到空闲的缓存页吧,这样效率太低了。所以,为了能够快速找到空闲的缓存页,可以使用链表结构,将空闲缓存页的「控制块」作为链表的节点,这个链表称为 Free 链表(空闲链表)。

有了 Free 链表后,每当需要从磁盘中加载一个页到 Buffer Pool 中时,就从 Free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上,然后把该缓存页对应的控制块从 Free 链表中移除。

管理脏页(Flush List)

  • Free Page(空闲页),表示此页未被使用,位于 Free 链表;

  • Clean Page(干净页),表示此页已被使用,但是页面未发生修改,位于LRU 链表。

  • Dirty Page(脏页),表示此页「已被使用」且「已经被修改」,其数据和磁盘上的数据已经不一致。当脏页上的数据写入磁盘后,内存数据和磁盘数据一致,那么该页就变成了干净页。脏页同时存在于 LRU 链表和 Flush 链表。

设计 Buffer Pool 除了能提高读性能,还能提高写性能,也就是更新数据的时候,不需要每次都要写入磁盘,而是将 Buffer Pool 对应的缓存页标记为脏页,然后再由后台线程将脏页写入到磁盘。

那为了能快速知道哪些缓存页是脏的,于是就设计出 Flush 链表,它跟 Free 链表类似的,链表的节点也是控制块,区别在于 Flush 链表的元素都是脏页。有了 Flush 链表后,后台线程就可以遍历 Flush 链表,将脏页写入到磁盘。

提升缓存命中率(冷热分离的LRU List)

简单的 LRU 算法的实现思路是这样的:

  • 当访问的页在 Buffer Pool 里,就直接把该页对应的 LRU 链表节点移动到链表的头部。

  • 当访问的页不在 Buffer Pool 里,除了要把页放入到 LRU 链表的头部,还要淘汰 LRU 链表末尾的节点。

但是简单的 LRU 算法并没有被 MySQL 使用,因为简单的 LRU 算法无法避免下面这两个问题:

  • 预读失效;

  • Buffer Pool 污染;

预读失效

根据程序的空间局部性原理,靠近当前被访问数据的数据,在未来很大概率会被访问到。所以,MySQL 在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘 IO。

但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读是白做了,这个就是预读失效。如果使用简单的 LRU 算法,就会把预读页放到 LRU 链表头部,而当 Buffer Pool空间不够的时候,还需要把末尾的页淘汰掉。

如果这些预读页如果一直不会被访问到,就会出现一个很奇怪的问题,不会被访问的预读页却占用了 LRU 链表前排的位置,而末尾淘汰的页,可能是频繁访问的页,这样就大大降低了缓存命中率。

Buffer Pool 污染

当某一个 SQL 语句扫描了大量的数据时,在 Buffer Pool 空间比较有限的情况下,可能会将 Buffer Pool 里的所有页都替换出去,导致大量热数据被淘汰了,等这些热数据又被再次访问的时候,由于缓存未命中,就会产生大量的磁盘 IO,MySQL 性能就会急剧下降,这个过程被称为 Buffer Pool 污染。

如果我们写了一个全表扫描的查询语句(页中每条数据会被访问最多一次),一下就将整个表的页加载到了 LRU 的头部。

冷热数据分离的LRU List

为了解决简单 LRU 链表的问题,InnoDB在设计 LRU 链表的时候,实际上是采取冷热数据分离的思想,LRU链表会被拆成两部分,一部分是热数据(又称new/young列表),一部分是冷数据(又称old列表)。如下图所示。冷数据默认占37%,通过innodb_old_blocks_pct 参数来设置。

基于冷热分离的LRU链表,这时新加载一个缓存页时,就不是直接放到LRU的头部了,而是放到冷数据区域的头部。那什么时候将冷数据区域的页移到热数据区域呢?

  • 如果是预读机制加载了一些不会被访问的页,慢慢的被淘汰掉就行了。如果预读的页被访问了,就将其放入热数据头部?这样是不行的全表扫描加载进来的页,必然是会被读取至少一次的,而且一页包含很多条记录,可能会被访问多次。

  • 所以 InnoDB 设置了一个规则,在第一次访问冷数据区域的缓存页的时候,就在它对应的描述信息块中记录第一次访问的时间,默认要间隔1秒后再访问这个页,才会被移到热数据区域的头部。也就是从第一次加载到冷数据区域后,1秒内多次访问都不会移动到热数据区域,基本上全表扫描查询某缓存页的操作1秒内就结束了。(间隔时间是由参数 innodb_old_blocks_time 控制的,默认是 1000毫秒

热数据区域中的页是每访问一次就移到头部吗?也不是的,热数据区域是最频繁访问的数据,如果频繁的对LRU链表进行节点移动操作也是不合理的。所以 InnoDB 就规定只有在访问了热数据区域的 后3/4 的缓存页才会被移动到链表头部,访问 前1/4 中的缓存页是不会移动的。

总结

  • LRU链表分为冷、热数据区域,前 63% 为热数据区域,后 37% 为冷数据区域,加载缓存页先放到冷数据区域头部。

  • 冷数据区域的缓存页第一次访问超过1秒后,再次访问时才会被移动到热数据区域头部。

  • 热数据区域中,只有后 3/4 的缓存页被访问才会移到头部,前 1/4 被访问到不会移动。

  • 淘汰数据优先淘汰冷数据区域尾部的缓存页。

LRU List 和 Flush List

Flush链表中的缓存页一定是在 LRU 链表中的,而 LRU 链表中不在 Flush链表 中的缓存页就是未修改过的页。可以通过下图来理解 LRU 链表和 Flsuh链表。

可以看到,脏页既存在于 LRU链表 中,也存在于 Flush链表 中。LRU链表 用来管理 Buffer Pool 中页的可用性,Flush链表 用来管理将页刷新回磁盘,二者互不影响。

即 Free + LRU(包含Flush)约等于 所有页数量,因为缓冲池中的页还可能分配给自适应哈希索引、Lock信息、Insert Buffer等

脏页刷脏

引入了 Buffer Pool 后,当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,但是磁盘中还是原数据。因此脏页需要被刷入磁盘,保证缓存和磁盘数据一致,但是若每次修改数据都刷入磁盘,则性能会很差,因此一般都会在一定时机进行批量刷盘。

  • 当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘;

  • 后台有专门的线程会定时从LRU链表尾部扫描一些缓存页,扫描的数量可以通过参数 innodb_lru_scan_depth 来设置。如果有脏页,就会把它们刷回磁盘,然后释放掉,不是脏页就直接释放掉,再把它们加回Free链表中。这种刷新页面的方式被称之为 BUF_FLUSH_LRU

  • Buffer Pool 没有空闲页时,需要将LRU List的尾部淘汰一个数据页淘,如果淘汰的是脏页,需要先将脏页同步到磁盘;这种刷新单个页面到磁盘中的刷新方式被称之为 BUF_FLUSH_SINGLE_PAGE

  • MySQL 认为空闲时,后台线程会定期将 Flush 链表适量的脏页刷入到磁盘;这种刷新页面的方式被称之为 BUF_FLUSH_LIST

  • MySQL 正常关闭之前,会把所有的脏页刷入到磁盘;

查看Buffer Pool状态

我们可以通过 SHOW ENGINE INNODB STATUS; 来查看 InnoDB 的状态信息。但是要注意,状态并不是当前的状态,而是过去某个时间范围内 InnoDB 存储引擎的状态。

缓冲池和内存信息

从输出的内容中,可以找到 BUFFER POOL AND MEMORY 这段关于缓冲池和内存的状态信息。

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 1099431936
Dictionary memory allocated 8281957
Buffer pool size   65535
Free buffers       1029
Database pages     63508
Old database pages 23423
Modified db pages  80
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 15278983, not young 2027514654
0.00 youngs/s, 0.00 non-youngs/s
Pages read 83326150, created 1809368, written 21840503
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 1 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 63508, unzip_LRU len: 0
I/O sum[833]:cur[0], unzip sum[0]:cur[0]
  • Total large memory allocated:Buffer Pool向操作系统申请的内存空间大小,包括全部控制块、缓存页、以及碎片的大小。

  • Dictionary memory allocated:为数据字典信息分配的内存空间大小,这个内存空间和Buffer Pool没啥关系,不包括在Total large memory allocated中。

  • Buffer pool size:缓存池页的数量,所以缓冲池大小为 65535 * 16KB = 1G

  • Free buffers:Free链表中的空闲缓存页数量。

  • Database pages:LRU链表中的缓存页数量。需要注意的是,Database pages + Free buffers 可能不等于 Buffer pool size,因为缓冲池中的页还可能分配给自适应哈希索引、Lock信息、Insert Buffer等,而这部分不需要LRU来管理。

  • Old database pages:LRU冷数据区域(old列表)的缓存页数量,23423/63508=36.88%,约等于 37%

  • Modified db pages:修改过的页,这就是 Flush链表中的脏页数量。

  • Pending reads:正在等待从磁盘上加载到Buffer Pool中的页面数量。当准备从磁盘中加载某个页面时,会先为这个页面在Buffer Pool中分配一个缓存页以及它对应的控制块,然后把这个控制块添加到LRU的冷数据区域的头部,但是这个时候真正的磁盘页并没有被加载进来,所以 Pending reads 的值会加1。

  • Pending writes:从LRU链表中刷新到磁盘中的页面数量,其实就对应着前面说的三种刷盘的时机:BUF_FLUSH_LRU、BUF_FLUSH_LIST、BUF_FLUSH_SINGLE_PAGE

  • Pages made young:显示了页从LRU的冷数据区域移到热数据区域头部的次数。注意如果是热数据区域后3/4被访问移动到头部是不会增加这个值的。

  • Pages made not young:这个是由于 innodb_old_blocks_time 的设置导致页没有从冷数据区域移到热数据区域的页数,可以看到这个值减少了很多不常用的页被移到热数据区域。

  • xx youngs/s, xx non-youngs/s:表示 made young 和 not young 这两类每秒的操作次数。

  • xx reads/s, xx creates/s, xx writes/s:代表读取,创建,写入的速率。

  • Buffer pool hit rate xx/1000:表示在过去某段时间,平均访问1000次页面,有多少次该页面已经被缓存到Buffer Pool了,表示缓存命中率。这里显示的就是 100%,说明缓冲池运行良好。这是一个重要的观察变量,通常该值不应该小于 95%,否则我们应该看下是否有全表扫描引起LRU链表被污染的问题。

  • young-making rate xx/1000 not xx/1000:表示在过去某段时间,平均访问1000次页面,有多少次访问使页面移动到热数据区域的头部了,以及没移动的缓存页数量。

  • LRU len:LRU 链表中节点的数量。

  • I/O sum[xx]:cur[xx]:最近50s读取磁盘页的总数,现在正在读取的磁盘页数量。

LRU List 信息

我们还可以查询 information_schema 下的 INNODB_BUFFER_PAGE_LRU 来观察LRU链表中每个页的具体信息。

SELECT * FROM information_schema.INNODB_BUFFER_PAGE_LRU WHERE TABLE_NAME = '`hzero_platform`.`iam_role`';

其中的一些信息如下:

  • POOL_ID:缓冲池ID,我们是可以设置多个缓冲池的。

  • SPACE:页所属表空间ID,表空间ID也可以从 information_schema.INNODB_SYS_TABLES 去查看。

  • PAGE_NUMBER:页号。

  • PAGE_TYPE:页类型,INDEX就是数据页。

  • NEWEST_MODIFICATION、OLDEST_MODIFICATION:LRU热数据区域和冷数据区域被修改的记录,如果想查询脏页的数量,可以加上条件 (NEWEST_MODIFICATION > 0 or OLDEST_MODIFICATION > 0)

  • NUMBER_RECORDS:这一页中的记录数。

  • COMPRESSED:是否压缩了

设置Buffer Pool大小

多线程访问 Buffer Pool 的时候,会涉及到对同一个 Free、LRU、Flush 等链表的操作,例如节点的移动、缓存页的刷新等,那必然是会涉及到加锁的。就算只有一个 Buffer Pool,多线程访问要加锁、释放锁,由于基本都是内存操作,所以性能也是很高的。但在一些高并发的生产环境中,配置多个 Buffer Pool,还是能极大地提高数据库并发性能的。

可以通过参数 innodb_buffer_pool_instances 来配置 Buffer Pool 实例数,通过参数 innodb_buffer_pool_size 设置所有 Buffer Pool 的总大小(单位字节)。每个 Buffer Pool 的大小就是 innodb_buffer_pool_size / innodb_buffer_pool_instances。InnoDB 规定,当 innodb_buffer_pool_size 小于1GB的时候,设置多个实例是无效的,会默认把innodb_buffer_pool_instances 的值修改为1

动态调整Buffer Pool大小

可以在运行时动态调整 innodb_buffer_pool_size 这个参数,但 InnoDB 并不是一次性申请 pool_size 大小的内存空间,而是以 chunk 为单位申请。一个 chunk 默认就是 128M,代表一片连续的空间,申请到这片内存空间后,就会被分为若干缓存页与其对应的描述信息块。

也就是说一个Buffer Pool实例其实是由若干个chunk组成的,每个chunk里划分了描述信息块和缓存页,然后共用一套 Free链表、LRU链表、Flush链表。每个chunk 的大小由参数 innodb_buffer_pool_chunk_size 控制,这个参数只能在服务器启动时指定,不能在运行时动态修改。

合理设置

在生产环境中安装MySQL数据库,首先我们一般要选择大内存的机器,那我们如何合理的设置 Buffer Pool 的大小呢?

比如有一台 32GB 的机器,不可能说直接给个30G,要考虑几个方面。首先前面说过,innodb_buffer_pool_size 并不包含描述块的大小,实际 Buffer Pool 的大小会超出 innodb_buffer_pool_size 5% 左右。另外机器本身运行、MySQL运行也会占用一定的内存,所以一般 Buffer Pool 可以设置为机器的 50%~60% 左右就可以了,比如32GB的机器,就设置 innodb_buffer_pool_size 为 20GB。

另外,innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数,主要是保证每一个Buffer Pool实例中包含的chunk数量相同。

比如默认 chunk_size=128MB,pool_size 设置 20GB,pool_instances 设置 16 个,那么 20GB / (128MB * 16) = 10 倍,这样每个 Buffer Pool 的大小就是 128MB * 10 = 1280MB。如果将 pool_instances 设置为 32 个,那么 20GB / (128MB * 32) = 5 倍,这样每个 Buffer Pool 的代销就是 128MB * 5 = 640MB

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1909558.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大话C语言:第29篇 指针

1 指针概念 指针:地址的变量化形式,其存储的是内存中某个存储单元的地址。它是地址的数值表示。 指针变量:一种特殊的变量,它专门用于存放变量的地址(即指针)。 注意,指针和指针变量的区别&am…

uniapp+uview实现手机端上传照片带水印(保姆级全过程)

目录 前言:实现思路 步骤一、在界面使用uview的u-upload组件、放置canvas标签 步骤二、在afterRead方法中获取照片url,并创建画布生成水印,再将生成水印的照片上传到服务器 1、afterRead方法 2、照片加水印的方法 3、上传照片至服务器 …

项目管理:这样制定计划,让你事半功倍

在项目管理中,制定计划是成功的关键,一个完善的计划不仅能够明确项目的方向和目标,还能为团队成员提供清晰的行动指南。 制定计划的四大要素——做什么、怎么做、何时做、谁来做,为这一过程提供了坚实的框架。而甘特图作为一种直观…

OS Copilot:新手测评体验

文章目录 前言一、OS Copilot(阿里云操作系统智能助手)简介二、测评体验总结OS Copilot 产品体验评测OS Copilot 产品功能反馈 前言 本文简单分享一下自己使用OS Copilot测评体验。 一、OS Copilot(阿里云操作系统智能助手)简介 …

普通人必看!AI绘画商业变现全攻略(附教程)

大部分的设计师除了主业以外,都会利用空余时间去接单做副业。 单子包括但不限于产品/品牌LOGO、电商产品图设计、海报、室内设计图等等,单价在几十到上千不等 引起了我的思考,我们普通人有没有机会,也能像他们一样去接单赚钱吗&a…

挖K脚本检测指南

免责声明:本文仅做分享... 目录 挖K样本-Win&Linux-危害&定性 Linux-Web 安全漏洞导致挖K事件 Windows-系统口令爆破导致挖K事件 --怎么被挖K了??? 已经取得了权限. 挖K样本-Win&Linux-危害&定性 危害:CPU 拉满,网络阻塞&…

Spring最早的源码

地址:Spring最早的源码

Uniapp组件使用的详细步骤

官方文档:uni-app官网 (dcloud.net.cn) 一般用到的组件都在这里: 看介绍里面的注意事项,有注意的地方认真查看,使用的时候例如,以下示例日历,把代码粘贴到你的文件里,然后下载组件 1&#xff…

商家转账到零钱分销返佣场景驳回处理办法

在处理商家转账到零钱分销返佣场景被驳回的问题时,商家需要了解驳回的原因,并采取相应的措施来解决这些问题。下面将详细介绍几种常见的驳回原因以及应对策略: 1. 多级分销模式问题 • 原因:如果业务模式涉及多级分销,…

数据安全治理:从库级权限申请到表级权限申请

背景 随着数据安全意识的提高,企业越来越重视数据治理和权限管理。传统数仓大多对库级别进行读写授权,仅对人工标记的敏感库进行表级别授权,但由于敏感等级是由人为标记,错误率较高,故期望将权限申请流程细化到表级申…

Datawhale AI 夏令营 机器学习挑战赛

一、赛事背景 在当今科技日新月异的时代,人工智能(AI)技术正以前所未有的深度和广度渗透到科研领域,特别是在化学及药物研发中展现出了巨大潜力。精准预测分子性质有助于高效筛选出具有优异性能的候选药物。以PROTACs为例&#x…

PHP调查问卷小程序系统源码

📊【问卷新风尚】调查问卷系统小程序,让数据收集更简单! 📱【指尖轻点,问卷即刻生成】 告别繁琐的问卷设计过程,调查问卷系统小程序让你在指尖轻点间,就能轻松创建出专业、美观的问卷。无论是…

文章SameStr(五):图5代码

“Publication Figure 5” 百度云盘链接: https://pan.baidu.com/s/15g7caZp354zIWktpnWzWhQ 提取码: 4sh7 Libraries Standard Import library(tidyverse) library(cowplot) library(scales) library(ggpubr)Special library(caret) library(randomForest) library(vip)…

Kudu分区策略

Kudu表的分区策略主要有三种:范围分区(Partition By Range)、哈希分区(Partition By Hash)和高级分区(Partition By Hash And Range)。这些策略都要求分区字段必须包含在主键中。 范围分区&…

内网-有道词典离线翻译安装

一、外部操作 1、在外部下载有道翻译桌面版 下载地址 2、安装到指定目录 3、登录下载离线翻译包 进入设置 -》离线增强-》离线翻译数据包-》翻译包 选择汉英互译 4、找到离线包位置 C:\Users\用户\AppData\Local\Yodao ,此处查看自己用户所在C盘位置 二、内网…

【ai】pycharm添加本地解释器

解释器右键可以重命名 系统的解释器竟然安装了4个 可以先使用python虚拟环境中的解释器 虚拟环境虽然是属于其他的项目的,但是看起来也可以给自己的当前项目用: 添加了 别的项目里虚拟环境的解释器

科研加速器FlowUs你的智能论文助手与协作伙伴|文献整理软件|论文进度跟踪|AI辅助研究|科研时间管理

FlowUs这个科研神器简直太棒了!试了一下,你的论文进度一目了然,就像有了一个智能助手在帮你规划每一天。日历视图让你的计划和截止日期清晰可见,再也不用担心错过任何重要的研究里程碑。 日历视图:FlowUs的日历视图功能…

DBeaver安装教程(开发人员和数据库管理员通用数据库管理工具)

前言 DBeaver 是一个通用的数据库管理工具和 SQL 客户端,支持 MySQL, PostgreSQL, Oracle, DB2, MSSQL, Sybase, Mimer, HSQLDB, Derby, 以及其他兼容 JDBC 的数据库。DBeaver 提供一个图形界面用来查看数据库结构、执行SQL查询和脚本,浏览和导出数据&a…

React+TS前台项目实战(二十七)-- 首页响应式构建之banner、搜索、统计模块布局

文章目录 前言一、 效果展示二、相关模块1. Statistic统计模块功能分析代码详细注释使用方式 2. Search搜索模块功能分析代码详细注释使用方式 3. banner模块功能分析代码详细注释使用方式 总结 前言 前面我们已经封装了这个项目基本要用到的全局组件了,现在就开始…

【国产开源可视化引擎Meta2d.js】图元

图元 又称画笔Pen。图形表达的基本元素,组成图像的基本单元。 构成 每一个图元由ID、名字、类型、属性(数据)组成。 ID 名为“id”的特殊属性,图元实例(画布上的图元对象)的唯一标识。拖拽到画布或创建…