MySQL Buffer Pool

总结自:小林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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3项目打包优化

前言 本文介绍在实际项目中进行打包优化过程 目前评分 good npm install web-vitals在App.vue加入如下代码测试网页性能指标 import { onLCP, onINP, onCLS, onFCP, onTTFP } from web-vitals/attributiononCLS(console.log) onINP(console.log) onLCP(console.log) onFCP(…

江协科技51单片机学习- p25 无源蜂鸣器

🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝​…

C语言 | Leetcode C语言题解之第223题矩形面积

题目: 题解: int computeArea(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) {int area1 (ax2 - ax1) * (ay2 - ay1), area2 (bx2 - bx1) * (by2 - by1);int overlapWidth fmin(ax2, bx2) - fmax(ax1, bx1), overlapHei…

气膜建筑如何在文化旅游行业中应用—轻空间

一、气膜建筑简介 气膜建筑是一种新型建筑形式,其主要结构由高强度膜材、空气支撑系统和固定系统组成。通过不断向膜体内部充气,使其形成稳定的内部压力来支撑整个建筑结构。气膜建筑因其建设速度快、成本相对较低、环保节能等优点,近年来在各…

DDR3 (四)

1 DDR3 8倍预取 DDR3相比DDR2外部IO时钟又提高了一倍,因此DDR3外部IO时钟是内核时钟的4倍,再加上双沿采样,因此DDR3可以实现8倍预取 2 DDR3 芯片位宽 DDR3使用8倍预取技术,指的是芯片位宽(DQ数据线位宽&#xff09…

HTML实现图片查看与隐藏

你好呀,我是小邹。 在网页设计中,提供一个直观且用户友好的图片查看功能是提升用户体验的重要一环。本文将详细介绍如何使用HTML、CSS和JavaScript来实现图片的查看与隐藏功能。通过本教程,你将学会如何让页面上的图片在点击时放大显示&…

旋转木马案例

旋转木马 如果接口需要的数据格式和原始数据提供的格式有差异 不要去改接口方法 也不要改原始数据 做一层中间件(数据处理函数/方法) <!DOCTYPE html> <html lang"zh-cn"><head><meta charset"UTF-8"><meta name"viewport…

【机器学习】(基础篇二) —— 监督学习和无监督学习

监督学习和无监督学习 本文介绍机器学习的分类&#xff0c;监督学习和无监督学习 监督学习 监督学习&#xff08;Supervised Learning&#xff09;是机器学习的一个核心分支&#xff0c;大部分的机器学习任务都是由监督学习完成的。 它涉及到使用带有标签的训练数据来训练模…

利用 Python 解析pcap文件

1、问题背景 当面对处理网络数据包分析时&#xff0c;pcap文件作为一个常见的文件格式存储了网络数据包的详细记录&#xff0c;它常常被用来进行网络故障排查或安全分析。为了充分利用这些数据&#xff0c;我们需要对其进行解析并提取出有价值的信息&#xff0c;例如数据包类型…

Why Can’t Robots Click The “I’m Not a Robot” Box On Websites?

Clicking a tiny box tells Google all they need to know about your humanity 你好,我是 Jiabcdefh。 if you’ve browsed the internet for any amount of time, you will likely come across a reCAPTCHA box. These boxes appear when you first enter certain websites…

关于Mars3d的入门

关于Mars3d的入门 一. 创建地球&#xff0c;加载瓦片图层二 矢量图层2.1 常用矢量图层2.1.1 GraphicLayer2.1.2 GeoJsonLayer 2.2 矢量图层的点击事件 三 矢量数据四 事件机制 一. 创建地球&#xff0c;加载瓦片图层 // 1. 创建地球let map new mars3d.Map("mars3dContai…

孕产妇(产科)管理信息系统源码 三甲医院产科电子病历系统成品源代码

孕产妇&#xff08;产科&#xff09;管理信息系统源码 三甲医院产科电子病历系统成品源代码 医院智慧孕产是一种通过信息化手段,实现孕产期宣教、健康服务的院外延伸,对孕产妇健康管理具有重要意义,是医院智慧服务水平和能力的体现。实行涵盖婚前检查、孕期保健、产后康复的一…

极客时间:使用Autogen Builder和本地LLM(Microsoft Phi3模型)在Mac上创建本地AI代理

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

C语言之数据在内存中的存储(1),整形与大小端字节序

目录 前言 一、整形数据在内存中的存储 二、大小端字节序 三、大小端字节序的判断 四、字符型数据在内存中的存储 总结 前言 本文主要讲述整型包括字符型是如何在内存中存储的&#xff0c;涉及到大小端字节序这一概念&#xff0c;还有如何判断大小端&#xff0c;希望对大…

python极速入门笔记(三)

1. 函数 #定义函数""" def 函数名&#xff08;参数&#xff09;:...return ** """ def calc_BMI(weight,height):BMIweight/(height**2)if BMI<18.5:category"偏瘦"elif BMI<25:category"正常"elif BMI<30:catego…

红外光气体检测:1.分子振动与红外吸收、检测系统的基本模型和红外敏感元件

分子振动与红外吸收 分子偶极矩的变化频率与分子内原子振动状态有关&#xff1a;μqd&#xff0c;其中μ是偶极矩&#xff0c;q是电荷&#xff0c;d是正负电荷中心距离。 分子在…

怎样优化 PostgreSQL 中对布尔类型数据的查询?

文章目录 一、索引的合理使用1. 常规 B-tree 索引2. 部分索引 二、查询编写技巧1. 避免不必要的类型转换2. 逻辑表达式的优化 三、表结构设计1. 避免过度细分的布尔列2. 规范化与反规范化 四、数据分布与分区1. 数据分布的考虑2. 表分区 五、数据库参数调整1. 相关配置参数2. 定…

深度学习-梯度下降算法-NLP(五)

梯度下降算法 深度学习中梯度下降算法简介找极小值问题数学上求最小值梯度梯度下降算法 找极小值问题在深度学习流程中深度学习整体流程图求解损失函数的目标权重的更新 深度学习中梯度下降算法简介 找极小值问题 引子&#xff1a; 我们训练一个人工智能模型&#xff0c;简单…

记录一次Nginx的使用过程

一、Docker安装配置nginx 1.拉取镜像 docker pull nginx2.创建挂载目录 启动前需要先创建Nginx外部挂载目录文件夹 主要有三个目录 conf&#xff1a;配置文件目录log&#xff1a;日志文件目录html&#xff1a;项目文件目录&#xff08;这里可以存放web文件&#xff09; 创建挂…