首页 > 数据库 > 数据库开发技术 > mysql每天一点点笔记(六)








在mysql中,如果使用innodb引擎,当访问某个页的数据时,会将完整的页的数据加载到内存中,然后进行数据的读写操作。完成数据操作后,mysql不会立即释放数据页对应的内存空间,因为短时间可能会再次访问该页面,将其缓存起来便可避免再次从磁盘读取,可以大大提高性能。这些数据页便存储在innodb buffer pool中,也称为缓冲池。
今天的学习便是围绕buffer pool展开,为了便于日后温故,个人将其划分为4个小模块:参数相关,链表管理,lru管理,数据脏页。但是这四个部分不是单一存在的,而是相互联系的。如下:

在mysql中,buffer pool是innodb向操作系统申请的一块内存空间。在进行申请内存空间时,为了提高性能以应对多线程高并发场景。buffer pool支持拆分,将一个大的buffer pool拆分为某干个单独的小的buffer pool实例。每个小的buffer pool相互独立,互不干扰。而在每个小buffer pool内部,为了支持动态调整buffer pool大小,以chunk为单位向操作系统申请空间,即一个小buffer pool由若干个chunk组成,每个chunk代表一片连续的内存空间,用图像描述就是这样的:

在5.7.5之前,buffer pool的大小只能通过启动时配置innodb_buffer_pool_size启动参数配置,无法动态配置,从5.7.5之后可以动态配置,命令为set global innodb_buffer_pool_size=2147483648(该值为2G)。理想状态来说,肯定是希望缓冲池能容纳所有的数据。但是这个是不现实的。一般建议设置该值为实例规格的65%-75%之间,设置太小,可能影响性能,设置太大,可能会导致oom。具体需要根据实际情况和业务进行合理的设置。
chunk的值通过innodb__buffer_pool_chunk_size参数控制,该值默认值为134217728(128M),该值只能在服务器启动时制定,不可动态修改。
在mysql中,如果是多线程高并发的场景,单一的buffer pool可能会带来性能瓶颈,因此mysql中innodb引擎支持多buffer pool设置,buffer pool实例的个数由innodb_buffer_pool_instances参数决定。但是如果innodb_buffer_pool_size的值小于1G,则设置innodb_buffer_pool_instances大于1是无效的,innodb会默认将innodb_buffer_pool_instances值改为1。
另外,理论上innodb_buffer_pool_size的值为innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的整数倍。在服务器启动时,如果不满足整数倍,服务器会自动调节innodb_buffer_pool_size大小,如果innodb_buffer_pool_chunk_size*innodb_buffer_pool_chunk_size值大于innodb_buffer_pool_size值,会调节innodb_buffer_pool_chunk_size大小为innodb_buffer_pool_size/innodb_buffer__pool_instances。

在innodb引擎的buffer pool内部,比较重要的三个链表是free链表,flush链表,lru链表,其他的链表暂时不考虑。不同的的链表承担不同的职责。free链表进行资源的管理,flush链表进行脏页的管理,lru链表进行淘汰机制的管理。下面进行细化。
在启动mysql服务器时,会完成对buffer pool的初始化过程,以chunk为单位向操作系统申请内存空间,并划分为若干个信息块和缓存页,缓存页默认大小为16kb,信息块大小约为缓存页大小的5%,不包含在buffer pool向操作系统申请的内存空间中。信息块是和缓存页一一对应的,记录缓存页的一些信息,如表空间信息,页号,链表节点信息,缓存页在缓冲池中的地址,锁信息等等。在chunk中,信息块存放在左侧,缓存页存放在右侧。内存空间如下:

free链表:
对于刚初始完的chunk而言,所有数据页都是空闲未使用的,此时会将所有缓存页对应的信息块加入到free链表中,信息表之间通过free-pre和free-next指针形成双向链表,然后free链表还有一个基节点,记录链表的头节点地址,尾节点地址,以及链表中的节点个数。基节点的内存空间需要额外申请,但是基节点的占用内存很小。
在innodb中,还维护了一个哈希表,其中key为表空间号+页号,value为缓存页。当访问某个页的数据页时,会先查找哈希表查看是否有对应的缓存页,如果有,则直接使用该缓存页,如果没有,则从free链表中选择一个空闲点缓存页分配给该页使用,从磁盘将对应的页加载到该缓存页使用。
flush链表:
当缓冲池中的某个缓冲页数据被修改后,便与对应的磁盘上的页不一致,此时称作脏页(dirty page)。考虑到性能问题,脏页不会立即回写到磁盘,但最终需要同步到磁盘。那缓冲池中缓存页很多,如何区别脏页和非修改页呢,innodb中提供了一个flush链表来管理脏页,当缓存页发生修改后,对应的信息块会被加入到链表中,flush链表与free链表结构类型,信息块之间通过flush_pre和flush_next形成双向指针。然后有一个额外的基节点,包含链表的头节点指针,尾节点指针以及节点数量信息。
lru链表:
通常buffer pool的大小是远小于实例数据大小的,因此会出现需要缓存的页占用的内存超过buffer pool大小的情况。此时就需要通过淘汰一部分页来释放空间。在mysql中使用的是升级版的lru。具体实现放到下一部分吧。

mysql中会有专门的页面清理线程将脏页刷新到磁盘,页面清理线程个数由innodb_page_cleaners决定,默认为4,但是如果该值大于innodb_buffer_pool_instances值,则会自动设置为与之相同的值。按照刷新路径,分为从lru链表的冷数据中刷新脏页到磁盘和从flush链表刷新脏页到磁盘两种。按照刷新时机可分为以下四种场景:redo日志写满,系统内存不足,mysql空闲,mysql正常关闭。分别进行细化:
两种刷新路径:
后台线程从lru链表的old区域刷新脏页到磁盘,后台线程会定时从lru链表尾部开始扫描缓存页,扫描的页面数由innodb_lru_scan_depth指定,如果发现脏页,则会将其刷新到磁盘,这种方式称为buf_flush_lru。
后台线程从flush链表中刷新一部分页面到磁盘,刷新速率受脏页比例和redo日志写盘速度两个因素决定。这种方式称为buf_flush_list。
四种场景:
当innodb的redo日志写满后,系统会停止所有更新操作,然后向前推进checkpoint,此时会将对应的脏页flush到磁盘以释放空间,然后才能继续进行写入。这种场景对业务影响最大,此时系统无法进行更新操作。需要尽量避免。
当内存不足时,如果淘汰的是脏页,则会将脏页写回到磁盘。这种情况是很常见的,通常不会影响性能,但是如果需要淘汰的脏页个数较多,也会影响性能,导致响应时间变长。
mysql空闲时刷新脏页和正常关闭这两种场景不会对性能产生影响。
在mysql中,当innodb_buffer_pool_size值小于1G时,默认innodb_buffer_pool_instances值为1,否则,则默认值为8,最大值为64,当在多线程和高并发场景下,划分多个buffer pool实例可有效提高性能,每个缓冲池管理自己的free链表,flush链表,lru链表即相关数据结构。一般来说,在总buffer pool大小大于或等于1G时,建议设置多个buffer pool实例,但具体设置多少可以根据测试找出最佳值。
在mysql中,不同阶段刷脏页的速率是不同的,mysql定义了两个参数用于标记脏页水位,其中,innodb_max_dirty_pages_pct_lwm表示低水位标记值,表示启用预刷新以控制脏页比率的脏页百分比,默认为0,即完全禁用预刷新行为。innodb_max_dirty_pages_pct表示高水位标记值,默认值为75,如果缓冲池中脏页百分比达到该值,则会全力刷新缓冲池中脏页。
在刷新脏页中,innodb会计算两个值,一个值为根据当前脏页的比例计算出的,假设为a,另外一个值为根据当前写入的日志的序号跟上一次checkpoint对应的序号的差值计算出来的,假设为b,a和比的值范围都在0-100之间,然后innodb引擎会取两者中的较大值,假设为c,然后刷脏页的速率等于innodb_io_capacity的值*c/100。其中,innodb_io_capacity值代表innodb可用的I/O大小,innodb_io_capacity_max表示I/O容量的上限。建议设置成磁盘读的iops值。如果该值设置不合理可能导致刷脏页特别慢,从而造成脏页累积,影响查询和更新性能,记得之前遇到过一次这种问题。可通过fio工具进行测试磁盘的iops值。
该参数是innodb用来控制buffer pool刷脏页时候是否将脏页附近的其他脏页一起刷入磁盘的,有三个值:0,1,2,其中,0表示刷脏页时不刷其附近的脏页。8.0时默认为0。1表示刷脏页时刷新相同范围内的连续脏页,5.7时默认为1。2表示会刷新新同范围内的脏页。1和2的区别在于2刷的区域更大一些。对于机械硬盘,刷临近脏页可以减少随机IO,但是刷邻近脏页会增加执行命令响应时间,对于固态硬盘等iops较高的设备,建议设置该值为0,即只刷自己。

上面的内容对innodb buffer pool的相关内容进行了一个简单的汇总,结合常见的参数,从innodb buffer的组成,到内部链表的管理以及缓冲页的淘汰,还有脏页的刷盘,大体上可以对buffer pool有了一个更深层次的认知,但是buffer pool是一个比较复杂的体系,还涉及到很多不常见但是也很重要的参数及机制,但是学习都是循序渐进的,不可能一步就能抵达终点,后面遇到了再总结吧。就这样,虽然周末快结束了,还是祝你们周末愉快啊。明天又得上班了,今晚可以适当放松下以补充元气,下周见。

END





原文作者:DBA修行之路;如有侵权,请联系管理员删除。