Mysql数据库Buffer Pool缓冲池详解

一、概述

        1、Mysql具有可插拔的存储引擎,默认为InnoDB。InnoDB也是最常用的存储引擎,Buffer Pool缓存池是InnoDB引擎特有的内存结构。Buffer Pool缓冲池的作用是要提高数据库的读写性能。

        2、mysql在启动时会向系统内存申请独立内存空间作为Buffer Pool缓冲池使用。因磁盘和内存性能存在很大差距,通过Buffer Pool缓冲池用于协调CPU速度和磁盘速度的鸿沟,可以大大提高数据库的读写性能。

        3、Buffer Pool缓冲池主要将磁盘的数据到加载内存中,且大多数的数据库的CRUD(增删改查)都是在缓存区内完成。Buffer Pool缓存池的大小默认为128M。可以通过

show variables like 'innodb_buffer_pool_size'

命令查询缓冲池大小,也可在启动服务器的时候配置inndb_buffer_pool_size修改缓冲池的大小。如innodb_buffer_pool_size = 268435456,将缓冲池大小设置为256M。官方认为Buffer Pool内存大小一般为机器内存的60%左右。

        4、数据查询时,不是直接从磁盘文件上获取数据信息,先查看Buffer Pool缓冲池中是否有想要的查询数据。若有,直接返回,若无,从磁盘文件中获取数据。查询到数据后将数据同步到Buffer Pool缓冲池中,便于下次查询无需访问磁盘。修改同理是将数据写到Buffer Pool缓冲池中,然后数据刷盘到磁盘文件。InnoDB是以页单位在磁盘上存储数据,一页数据为16kb。那么Buffer Pool也是以页单位存储数据。

二、组成

        Buffer Pool缓冲池主要有数据页、索引页、undo页、锁信息、自适应哈希索引、插入缓存、数据字典几部分组成

                        

1、数据页

        当进行查询的数据不在缓冲池时,就会将获取的数据在磁盘中对应的页加载到Buffer Pool中。这些数据对应的页就是数据页。当对数据页中的数据修改时,数据页就会变成脏页(数据修改未刷盘的缓冲页),而不是直接操作磁盘上的文件页。通过以页为单位交互大大提高了性能。

2、索引页

        Buffer Pool缓冲池中不但存储数据页,还存储了索引页。因为不能保证每次查询都能从缓冲池的数据页中获取想要的数据,若无,就需从磁盘上的数据文件中进行I/O操作,若查询命中了索引,那又该如何知道索引的根节点在磁盘上哪个位置呢。就需索引页来解决这个问题。mysql启动时,将数据库中的索引根节点放到缓冲池的索引页中,当查询命中索引时,就不需在整个磁盘找索引的根节点了。包括主键索引(聚集索引)和辅助索引(非聚集索引)。

3、undo页

        主要记录事务的回滚操作相关信息,存储旧版本的数,用于支持事务的ACID属性中的隔离性和持久性,常用与事务回滚操作。数据库修改操作时,undo页记录与操作相反的操作,例如:

进行insert操作,undo则记录delete操作

进行delete操作,undo则记录insert操作

进行update操作(a改b),undo则记录update操作(b改a)

4、锁信息

        在Buffer Pool缓冲池中,InnoDB会维护锁结构、并发事务的链表等相关锁信息,以追踪哪些数据页或行被锁定,以及锁的类型(共享锁或排它锁)。InnoDB通过锁来确保并发访问时数据的一致性和完整性。

5、自适应哈希索引

        默认下索引页采用B+Tree的结构,可以大大提高查询性能。当某些索引值被频繁访问时,InnoDB会将这些索引存储在自适应哈希索引中,以加快对这些值的查询。自适应哈希索引是InnoDB自动生成,针对的是热点索引页,且生成条件比较苛刻,访问模式如:

where a = xxx
where a = xxx and b = yyy

上面两个模式不能交替执行,不然无法生成自适应哈希索引,在一定时间范围内一般有两种情况生成:

以某个模式访问100次
以某个模式访问 n 次(n = 页中记录 / 16)

6、插入缓冲

        当向一个包含非聚簇、非唯一索引的表中插入、修改数据时,不是直接操作索引页,需判断当前页是否存在,若存在,直接操作索引页。若不存在,InnoDB不会立即将索引键插入到索引页中,而是将其存储到插入缓冲中。然后以一定频率进行插入缓冲和辅助索引页合并,大大提升非聚簇索引的操作性能。

7、数据字典

        MySQL数据库启动时,会自动从硬盘中将系统表相关信息加载到Buffer Pool缓冲池中,有了数据字典,这样当我们使用show indexshow tables相关命令就能查到表、索引相关的信息,主要分为以下:

SYS_TABLES:存储所有InnoDB表信息。
SYS_COLUMNS:存储所有用户定义的表字段信息。
SYS_INDEXES:存储所有InnoDB引擎表索引信息。
SYS_FIELDS:存储所有索引的定义信息。

三、内存管理

1、控制块

        InnoDB在系统中为Buffer Pool缓冲池申请了一个连续的内存空间,内存被划分为一个个缓冲页(缓冲池以页为基本单位与磁盘交互)。每个缓冲页除了存储页数据以外,其内部还分配了一个控制块,控制块与缓冲页是一对一关系。

        控制块主要存储缓冲页信息,包括数据页所属表空间、页号、缓存页地址等信息。控制块与缓存页关系图如下:途中的内存碎片是因为剩余空间不够一对控制块和缓存页的大小。所以剩余内存空间称为内存碎片。

2、存储方式

        Buffer Pool缓冲池在mysql启动时,InnoDB向操作系统申请了一块连续的内存空间。第一次启动时缓冲池数据为空。当数据加载到Buffer Pool内时,在内部需要对内存空间进行维护,存储数据时需判断哪些空间已被使用,哪些空间可以使用。维护空间的方式有以下几种:

1. free链表(free List)

        InnoDB的缓冲池内存被分割为一个个页,并不是所有页都能被使用。有些页被利用,有些页空闲。在内部使用free链表对空闲页进行管理,free链表为双向链表,便于快速查找并使用。不在链表中的控制块代表其对应的缓存页已被使用。

        当磁盘页数据刷入到Buffer Pool缓冲池中时,就会从free 链表中查找是否存在空闲页,若有,就从free 链表中取出使用(移除),找到对应的缓存页。若无,需通过LRU链表的尾部数据页。后面会讲到。对free链表的头节点解释:

head:指针,指向 Free List 的第一个控制块。
tail:指针,指向 Free List 的最后一个控制块。
count:数字,记录 Free List 的节点数量。

2. flush链表(flush List)

        free链表用来记录哪些空间可以使用。在Buffer Pool缓冲池的使用flush链表用来记录管理修改过缓存页还未刷到磁盘的这些缓存页。在数据库启动时,flush链表为空。

        在InnoDB内部为提升内部性能,修改数据执行后,不会立即进行刷盘操作。被修改的缓存页被称为“脏页”。脏页包含新增、修改、删除的操作。脏页数据与磁盘数据是不一致的,这些脏页通过flush 链表管理。通过后台线程将数据异步刷盘到磁盘文件上。

3. LRU链表(LRU List)

        LRU链表用来管理已读取的页。当数据库启动时,LRU链表也为空,这些空闲页都在free链表中。当数据页加载到Buffer Pool中,free链表中的空闲页就可以使用,当free链表中没有空闲页就会根据LRU算法淘汰LRU链表的尾部页,将内存分配给新页。

        当磁盘页加载到Buffer Pool缓冲池中,未发生修改,说明缓冲页是干净的(干净页)。脏页数据刷盘到磁盘后也会变成干净页。需要强调的是当干净页变成 脏页时,此时脏页不会从LRU链表中移除,脏页将同时存在于flush链表和LRU链表中。

        LRU链表管理缓存页是通过LRU算法实现,LRU算法将访问频率较高(经常使用)的缓存页放到链表的头部,将访问频率低(很少使用)的缓存页放到链表尾部。当可用的空闲页不足时,就会淘汰LRU链表尾部的缓存页。

        LRU链表使用的LRU算法与传统的LRU算法有区别的,主要是传统LRU算法有预读无效 和 Buffer Pool污染两个问题。

预读无效:Buffer Pool加载数据页时,会将相邻数据页一起加载缓冲池。会将预读的数据页放到头部,可能会出现预读数据页很少会用到,从而降低LRU链表使用性能。

Buffer Pool污染:偶尔一次大数量的查询(如全表扫描),直接出现许多不常用数据出现在LRU链表头部或将链表中所有数据页都替换(缓冲池空间有限)导致本身的热点数据被移除,降低LRU链表使用性能。

LRU链表大致划分图:

        为解决传统LRU算法的两个问题,InnoDB采用特殊LRU算法(划分区域的LRU链表)来解决这两个问题。如上图将LRU链表划分为两个区域,midpoint左边是NEW区(热点页数据区),右边是OLD区(冷点页数据区),类似JVM的年轻代和老年代。热点区数据使用频率高,冷点区数据使用频率低。midpoint指针位于链表5/8处(37:63)。这个比例可以通过innodb_buffer_poll_pct参数调整。热点数据定义为1s内访问三次,热数据长时间不访问就会变成冷数据,放到LRU链表尾部。

        刚从磁盘加载进来的缓存页也是存储在OLD区。InnoDB通过时间参数innodb_old_blocks_time控制页读取到midponit位置时,等待多久才会加入到NEW区,默认时间间隔为1000ms,若后续访问时间与第一次访问时间不在时间间隔内,那该缓冲页就会移动到NEW区头部。  

3、多个Buffer Pool实例

        在多线程环境下,访问Buffer Pool中各种链表都需加锁处理。多线程高并发下单一Buffer Pool会影响请求的处理数速度。可以将Buffer Pool拆分成多个小的Buffer Pool实例。它们都是独立内存空间、独立管理各种链表,从而提高并发处理能力。

可通过设置以下值来设置Buffer Pool实例数,以及获取每个实例的大小。

#设置buffer poool实例的个数
innodb_buffer_pool_instances;
#获取每个实例的大小
innodb_buffer_pool_size/innodb_buffer_pool_instances

四、常见参数配置

        mysql允许通过修改配置文件(my.cnf或my.ini)来修改缓冲池的大小和其他参数。需注意只用mysql服务重启后才能修改生效;

        innodb_buffer_pool_size:用于指定缓冲池的总大小,可以根据服务器物理内存大小和数据库工作负载来合理的设置这个值,一个常见的经验法是将缓冲池的总大小设置为服务器总内存的50% 到80%。

        innodb_buffer_pool_instances:用于指定缓冲池被分割成多少buffer-pool实例,每个实例的大小为 innodb_buffer_pool_size/innodb_buffer_pool_instancee。将缓冲池分割成多个实例可以减少内部锁的竞争,特别是在高并发场景下。当然实例个数并不是越多越好,mysql要进行内部管理,mysql规定buffer-pool的实例最少为1个,最多为64个。可以通过show engine innodb status\g查看buffer-pool的全部信息。

        innodb_old_blocks_pct:这个参数定义了缓冲池中“旧”数据块所占百分比。当缓冲池满时,决定了哪些数据块应该被替换掉。默认是整个LRU链表的5/8。左边为热点区,右边为冷点区,类似JVM的年轻代和老年代。

五、总结

Buffer Pool是mysql数据库InnoDB存储引擎中一个关键的性能优化组件,通过合理配置和优化Buffer Pool可以显著提高数据库的性能和可靠性。若innodb_buffer_pool_size设置过大,可能会导致系统交换(swapping),反而会降低性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值