Mysql - InnoDB三大特性之Buffer Pool缓冲池

    Buffer Pool(简称BP)是InnoDB的三大特性之一,是innoDB内存中最大的一块内存,还有两个是 自适应Hash索引(Adaptive Hash Index)双写缓冲区(Doublewrite Buffer)。我们知道磁盘的寻址访问时间是毫秒级别的,而内存寻址访问是纳秒级别的,访问速度是差万倍到十万倍。所以使用好缓存,让有限的内存以最高的命中率处理数据请求,就是Buffer Pool存在的意义。自适应Hash索引(Adaptive Hash Index)中我们理解了,InnoDB引擎从内存增加了hash索引(散列表数据结构)让访问量比较高的、适合散列表数据结构特点的访问,能在内存中以近似O(1)的时间复杂度内解决查询问题。其他请求(或者说大部分)请求还是需要使用B+树种进行处理,时间复杂度为O(logN),  还可能涉及2~3次的磁盘访问。其实大多数的数据库处理请求(sql)都是在B+树中处理,而最小存储单一就是页,怎么能最好的利用好内存,提高数据的命中率,就是Buffer Pool内部需要实现的。

    数据库需要处理那些请求,无非就是 select、insert、update、delete,分为读写请求分为下面的方式进行优化:

读请求: 使用变种的LRU缓存淘汰策略,让热点数据停留到缓存中,增加缓存命中率;针对select进行优化,总是让需要查询的数据页驻留在内存中,提高性能(但是复杂的sql还需要其他额外的处理流程,但是都需要访问数据缓存页,这些后面详细分析,比如join的流程,排序的过程等)。

写请求:change buffer(delete、update、insert)提高写请求的响应速度,但是只对普通索引有效,而唯一索引则失效,每次必须真实地更新。具体流程参见:Mysql - 普通索引与唯一索引之间性能差别change buffer

 

    与理解innoDB的其他特性一样,我们还是先看看Buffer Pool在缓存InnoDB架构图中的位置:

    既然Buffer Pool如此的重要,控制好了个人理解是对数据结构的一种侧面优化,那么该部分内存,在允许的范围内肯定是越大越好。使用下面参数控制:

innodb_buffer_pool_size:引擎的缓存池大小,一般为物理内存的 60% - 80%;

innodb_buffer_pool_instances:IBP(InnoDB Buffer Pool)的实例个数,Linux系统,如果 innodb_buffer_pool_size 大小超过 1GB,innodb_buffer_pool_instances 值就默认为 8;否则,小于1G默认为1,防止有太多小的instance从而导致性能问题;

innodb_page_size = 16k,InnoDB的页大小默认为16K, 可设置的值有: 64KB,32KB,16KB(默认),8KB和4KB;

innodb_buffer_pool_chunk_size = 128M,控制内部的块的大小,默认128M;

 

    Buffer Pool中存储了索引页、数据页、undo页、插入缓冲(insert buffer)、修改缓冲(change buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)、数据字典信息(data dictionary)等信息,如下图:

    当数据查询修改后,内部需要将热点数据驻留在内存中,就是基于下面的LRU缓冲淘汰算法。当执行update、delete、insert语句后,缓冲中的数据与磁盘中的数据就不一致了,称为脏页。具体脏页的刷新策略比较复杂,专门进行分析。通过Free List记录空页当然本身也是干净页 、FLU List记录脏页的数据,其他的还有Unzip LRU List这里先不关注。更为详细的 InnoDB内部结构图可以参见(不是我画的,我只是搬运工):

 

Mysql中的LRU缓存淘汰算法

    缓存淘汰算法比较多,常用的也比较有效的就是LRU,只需要维护一个链表的数据结构,最新被访问的使用头插入法放到链表头部,当数据存储到达了buffer pool的阈值时,淘汰链表尾部的缓存页即可。我们知道链表数据结构的修改和删除动作的时间复杂度是O(1),但是查询的时间复杂度是O(N),如果我们要查询数据时就需要先查询缓存页中的数据。如果只是判断是否在缓存页中我们可以使用布隆过滤器(基于位图)进行优化,但是如果需要真正需要查询缓存页中的数据时,就需要执行时间复杂度O(N)的操作。此时就想到了hash的数据结构,读写的时间复杂度近似O(1)。这些都是基于数据结构理论的设想,下面是myql的实现:

    InnoDB的buffer pool当超过1G大小时,就会按照多instance存储,并且通过锁控制访问的数据安全性。每个Buffer Pool Instance有一个page hash链表,通过它,使用space_id和page_no就能快速找到已经被读入内存的数据页,而不用线性遍历LRU List去查找。我们完全可以理解成底层的LRU就是基于 散列表 + 双向链表实现的。类似java 中的LinkedHashMap,下面是其数据结构:

 

    Mysql的LRU缓存淘汰算法虽然数据结构基于 散列表 + 双向链表,但是实现本身却是变体的,主要基于下面两个原因:

1、Mysql中有时候会进行全表的扫描查询操作,即会将所有表数据在查出来一遍,比如支持报表服务查询等请求。具体的查询流转流程可以查看Mysql - 大表全表查询过程(分析底层的数据流转过程)。针对这种情况,也会将查询到的数据页全部在 LRU中走一遍,那么后续查询则需要一段时间才能恢复缓存命中率。

2、基于磁盘的局部性原理,会将一部分非真实想要查询的数据加载到内存中; 还有Mysql本身的预读功能,虽然读取数据时周围数据页被访问的可能性会比较高,一次性加载提升io性能,但是这些数据不一定就会真正后面需要执行查询的数据(即,存在降低命中率的可能性)。

    预读:InnoDB在I/O的优化上有个比较重要的特性为预读,预读请求是一个i/o请求,它会异步地在缓冲池中预先回迁多个页面,预计很快就会需要这些页面,这些请求在一个范围内引入所有页面。InnoDB以64个page为一个extent,但是预读会一次io加载额外的page到缓存中。预读分为两种:1、线性预读(Linear read-line) 可以通过参数 innodb_read_ahead_threshold 控制预读触发的时间,将将下一个 extent(64page)预读加载到Buffer Pool中。 我们可以使用参数:show variables like 'innodb_read_ahead_threshold' 查询该配置值。2、随机预读(random read-ahead) 表示当同一个extent中的一些page在buffer pool中发现时,Innodb会将该extent中的剩余page一并读到buffer pool中。在 Mysql 5.5进行废弃,所以不需要过多关注。

    针对以上的情况,Mysql将缓存的链表分成young、old区。针对上面的情况,所以会将数据加载到old区,只有当满足参数配置参数innodb_old_blocks_time:old区升级为young区的时间判断。默认值1000,单位毫秒;即如果上面的情况,隔离默认1秒之后又被加载一次,那么才会真正加载的young区域。young区域和old区域默认比大致为:5/8 buffer pool 和 3/8 buffer pool。整体lru架构图,直接使用官方的:

   

Buffer Pool缓存的关联,关乎着执行性能的高低。我们可以使用命令 show engine innodb status 查询其统计信息(更多关于配置项的说明信息可以查看官方链接:https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html Table 14.2 InnoDB Buffer Pool Metrics):

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2198863872
Dictionary memory allocated 776332
Buffer pool size   131072
Free buffers       124908
Database pages     5720
Old database pages 2071
Modified db pages  910
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4, not young 0
0.10 youngs/s, 0.00 non-youngs/s
Pages read 197, created 5523, written 5060
0.00 reads/s, 190.89 creates/s, 244.94 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not
0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read
ahead 0.00/s
LRU len: 5720, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

 

    自己觉得尽量了,但是还是觉得写的比较混乱,现在梳理一下主线,做一个总结:

1、innodb的Buffer Pool建议分配物理内存的 60~80% ,因为缓存的寻址性能是磁盘的万到数十万倍。

2、自适应hash索引本质也是Buffer Pool的一部分,他是innoDB层面对 hash索引数据结构的支持。因为hash索引读写的时间复杂度近似O(1),是对B+树索引的优化。

3、Mysql作为数据库,查询(select语句)是必然要支持的,那么就需要尽可能的提升缓存的命中率。 Mysql针对扫描全数据表,预读(乃至磁盘局部性原理)等情况可能降低缓存的命中率,Mysql的LRU进行了变体处理(非为young、old区)。

4、针对写请求:比较早的Mysql版本可以使用insert buffer对insert语句加速,后面的版本使用change buffer兼容insert、update、delete操作。这部分与是否存在唯一索引有关,比较复杂可以参考:Mysql - 普通索引与唯一索引之间性能差别change buffer

5、针对上面写操作,则可以导致脏页的问题(即内存中的数据页与磁盘中的数据页不一致)。与刷脏页策略有关,也比较复杂,后面专门写博客分析。

6、我们学习mysql的内部核心,就是为了在写好sql的情况下,还能针对具体情况进行优化。那么Buffer Pool的状态是我们需要关注的,需要多使用 show engine innodb status关注各项统计信息。

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值