原创mysql内核源代码深度解析 缓冲池 buffer pool 整体概述

本人原创文章,转载请注明出处。

mysql的内存管理庞大而先进,这在mem0pool.c文件的开头注释中都有说明,粗略的可以分成四部分,包含9大块:

buffer pool,

parsed andoptimized SQL statements,

data dictionarycache,

log buffer,

locks for eachtransaction,

hash table forthe adaptive index,

state andbuffers for each SQL query currently being executed,

session foreach user, and

stack for eachOS thread.

9大块通过4部分进行管理

A solution tothe memory management:

 

1. the bufferpool size is set separately;

2. log buffersize is set separately;

3. the commonpool size for all the other entries, except 8, is set separately.

也就是缓冲池,redo日志缓冲,普通池和8(用户session信息,可看做一部分)

redo日志缓冲由redo部分单独管理,bufferpool也就是缓冲池是一个复杂的部分,内容很多,普通池上面说了,除了8,和1,2.其余的都归它管。上面这个结构就是mysql内存子系统的完整图景。如图所示:

本篇从缓冲池(buf pool)讲起,然后会讲解common pool和插入缓存。

在做完整、全面而详细的缓冲池构架分析之前,必须先要穷举所涉及到的全部bufpool子系统组件和重点,内容非常之多。

(1)Buf pool五大组成模块

首先,bufpool子系统可以分成最基本的五个模块组件。

缓冲池例程常规管理(buffer pool routines),进行buf pool(多)实例管理,协调另外四模块进行运行,对存储、事务、redo日志、系统主线程等外部子系统等提供调用函数接口,是五个模块的核心。

LRU链表管理(LRU replacement algorithm),buf pool中基础控制存储单元(下文即将详细介绍)如buf_block_struct和buf_page_struct,都需要在对应的非压缩与压缩LRU链表中进行管理,从设计思想上需要实现加入LRU链表节点、删除LRU链表节点两个基本点,进而需要实现分配块、释放块两个高级功能,由此衍生出15个可以被外部模块调用的重要主函数;另外,虽然不为外部接口调用,但与主函数关联的仍旧有14个重要辅助函数(主函数的全部实现与嵌套细节),这两部分相当于LRU全部43个函数中的60%以上。

刷新机制(flush algorithm),bufpool部分与底层IO交互最紧密的模块,直接完成刷新动作,提供的各种刷新接口分别被子系统之内的所有模块调用。

刷新方式可以分为:通过LRU链表刷新的方式(BUF_FLUSH_LRU)和通过flush链表刷新的方式(BUF_FLUSH_LIST)。从功能来说,flush模块也必然包含flush链表的管理功能,实现链表节点的插入,删除,进而实现单块刷新、临近页刷新、doublewrite组件刷新这三个主要功能,并实现外部接口,供事务部分(和mini事务部分)commit动作中实现flush链表脏块的插入。

伙伴系统(Binary buddy allocator for compressed pages),在bufpool里面,伙伴系统并不是实现统一内存分配管理的单元,它的作用仅仅限于分配buf控制块(buf_page_struct)所需压缩页内存分配动作,但仍具有不可小视的巨大作用。Bufpool最终的目标是为实现各种供文件存储系统、事务管理、主进程调度单元等使用的外部接口,而这些部分基本都需要或多或少的对buf控制块中所管理的页中的数据记录(buf_block_struct->buf_page_struct.zip.data)进行读写操作,在伙伴管理分配内存之后,底层还要根据系统运行需要,进行压缩页到非压缩页的转换,可以说是极端重要的一个组件。

读缓存(buffer read),实现的功能简单直接,单页异步读、随机读、线性预读,运行这些功能的同时也存在着根据不同情况,进行的LRU链表(LRU和unzip_LRU)对读取到的页的控制块(buf_block_struct或buf_page_struct)进行节点插入、删除、置于链表末尾等操作,有LRU操作的地方自然也可能存在flush链表的刷新动作。

(2)四个重要结构体

其次,在子系统五大组件运行的过程中,涉及到四个重要的结构体,

缓冲池控制实例(buf_pool_struct),实现bufpool实例管理的核心数据结构,包含上文提到下文会详细描述的6大链表表头(UT_LIST_BASE_NODE_T),包含异步IO刷新条件变量(os_event_t)、LRU和flush链表互斥体、压缩与非压缩页hash表,上面这些仅仅是最重要部分。

struct buf_pool_struct{

。。。。。。

       ulint             LRU_old_ratio;

       mutex_t              LRU_list_mutex;

       rw_lock_t    page_hash_latch;

       mutex_t              free_list_mutex;

。。。。。。

       hash_table_t*     page_hash;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)flush_list;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)free;

。。。。。。

       UT_LIST_BASE_NODE_T(buf_page_t)LRU;

。。。。。。

};//结构体的内容难以一时尽述,下文会在每个模块中需要用到的部分时逐步引用并逐一加以解释。

底层内存分配单元(buf_chunk_struct),在无数的5.5以上版本mysql内核分析文章中,这个结构体都是最容易被人忽视的部分,但从某个意义上说是极其重要的,因为它最贴近bufpool的底层—OS内存分配,所有的buf控制块(buf_block_struct),都是挂载到6大链表中并可以直接通过的(buf_pool_struct)进行管理,但是这些结构体最初的内存分配动作,都是在(buf_chunk_struct)结构体的初始化阶段完成的,(buf_chunk_struct)又是(buf_pool_struct)的最基础最底层的内存分配单元。虽然这部分在代码量级上可说是“无足轻重”,但就重要性来讲绝对不能无视。

struct buf_chunk_struct{

       ulint             mem_size;    /*!< allocated size of the chunk */

       ulint             size;              /*!< size of frames[] and blocks[] */

       void*            mem;            /*!<pointer to the memory area which

                                   wasallocated for the frames */

       buf_block_t*      blocks;         /*!<array of buffer control blocks */

};//chunk结构体内容较少,此处已经列举全部内容,下文会根据模块引用做说明。

非压缩页控制块(buf_block_struct),在网易老姜所著的《mysql内核 innodb存储引擎卷1》中对此结构体做过描述,随着版本的变迁和mysql功能的递进,页控制块也进化了,非压缩页控制块(buf_block_struct)的管理与压缩页控制块(buf_page_struct)单独分开,前者包含后者的结构体引用、物理页帧地址、unzip_LRU链表节点(UT_LIST_NODE_T)、读写锁、互斥体等重要对象,是实现bufpool各种核心接口功能的最关键控制单元。

struct buf_block_struct{

       buf_page_t  page;            /*!<page information; this must

                                   bethe first field, so that

                                   buf_pool->page_hashcan point

                                   tobuf_page_t or buf_block_t */

       byte*            frame;          /*!< pointer to buffer frame which

                                   isof size UNIV_PAGE_SIZE, and

                                   alignedto an address divisible by

                                   UNIV_PAGE_SIZE*/

。。。。。。

};//本结构体中其他的部分都先不做任何的列举,但请一定记住上面这两个,page非压缩页结构体引用压缩页结构体的重要句柄,同时也是在一个重要函数中进行强制转换操作((buf_block_t*) bpage)最关键的部分!至于frame则是bufpool部门真正服务的核心所在,这是非压缩页(数据页,undo页,特殊页。。。。。。)的页帧地址。一旦某页记录读入通过read模块读入链表进行管理之后,那么它的所有modify操作等同于都是针对这个页帧里面做内存修改,至于写回磁盘是异步(同步)IO需要考虑的事情(详解文件存储子系统的时候会对IO机制作出完整说明)。

压缩页控制块(buf_page_struct),理论上讲,全部的非压缩页只是压缩页的子集(实际情况有待本人进一步验证),因为在进行核心操作的时候,都是在非压缩页中进行,因此压缩页控制块(buf_page_struct)并不包含互斥体,但是为了保证与(buf_block_struct)的一致,需要进行锁计数器的实现,另外,它还包含对应物理页的表空间id、表空间内页的偏移量、页状态、刷新类型(上文介绍过BUF_FLUSH_LRU和BUF_FLUSH_LIST)、压缩页对应引用、所在hash表、5大链表(unzip_LRU在非压缩页结构体对象(buf_block_struct)所以此处才是剩下的5个链表)的子节点(UT_LIST_NODE_T)、以及是否处于OLD_LRU端(LRU链表结构old部分)是否崩溃页初次访问时间等细节,这些也仅仅是列举出的最重要结构体对象而已,不是全部。

struct buf_page_struct{

       unsigned      space:32;     /*!<tablespace id; also protected

                                   bybuf_pool->mutex. */

       unsigned      offset:32;     /*!<page number; also protected

                                   bybuf_pool->mutex. */

。。。。。。

       unsigned      flush_type:2;      /*!< if this block is currently being

                                   flushedto disk, this tells the

                                   flush_type.

                                   @seeenum buf_flush */

       unsigned      io_fix:2;       /*!<type of pending I/O operation;

                                   alsoprotected by buf_pool->mutex

                                   @seeenum buf_io_fix */

       unsigned      buf_fix_count:19;/*!< count of howmanyfold this block

                                   iscurrently bufferfixed */

。。。。。。

       UT_LIST_NODE_T(buf_page_t)free;

       UT_LIST_NODE_T(buf_page_t)flush_list;

       UT_LIST_NODE_T(buf_page_t)zip_list;

。。。。。。

};

(3)六个重要链表

再次,子系统组件与基础控制结构体运作的过程要实现各种复杂功能,必然无法脱离6个重要的链表,

空闲链表(buf_pool->free),bufpool最重要的三个链表之一,上文已经多次提到LRU和flush等链表,但未提到空闲链表,实际上它是在系统初始化阶段bufpool进行初始化后唯一显式调用链表初始化函数进行init操作的唯一bufpool链表。系统内的第一个LRU链表块,必然是从free链表中获取到的,当flush模块脏页刷新完成,LRU链表节点就会被清除或者移动到LRU链表结尾等待清除,清除LRU之后的节点仍旧是回归到free链表内。

LRU链表(buf_pool->LRU),bufpool最重要的三个链表之二,当有LRU链表为空时,必然从free链表获取空闲节点,并进行异步IO读将页读入bufpool,并加入LRU链表,LRU链表长度过大的情况下,会进行尾部刷新,刷新失败会进行更彻底的直接通过LRU进行脏页刷新(BUF_FLUSH_LRU方式),flush链表节点得到释放脏页完成刷新,并同时把LRU链表的脏块也完成移除。可以说LRU和free、flush三个链表、5个模块有之间有着千丝万缕的联系。

非压缩LRU链表(buf_pool->unzip_LRU),本链表实际是LRU链表的一个子集,在压缩页控制块(buf_page_struct)中的压缩页需要进行解压缩以进行各种记录级读写操作时,该链表将发挥作用,因此可以说,插入到了unzip_LRU链表就一定页在LRU链表中,反之则未必。

脏块链表(buf_pool->flush_list),bufpool最重要的三个链表之二,实际上也是LRU链表的子集,所以读入bufpool的页都通过压缩的或者非压缩的控制块进行管理,最初一定是在LRU链表中,当事务部分(和mini事务部分)完成commit操作时候,实际上就意味着内存写的成功,脏页必然要加入flush链表,并等待异步IO线程(系统主线程子系统组成之一)进行刷新操作(linux原生异步IO和作者Heikki Tuuri自己用条件变量实现的模拟异步IO,buf_pool_struct提到过,存储部分会做详细说明)动作。

未修改压缩块链表(buf_pool->zip_clean),本链表以目前的源代码来看仅用于调试功能。

伙伴系统空闲链表(buf_pool->zip_free[]),bufpool6大链表中最特殊的一个,链表的根节点可以看做“是一个指针数组”,伙伴系统的精髓就在于按照2的倍数进行紧邻内存块的合并和拆分,进而达到高效管理、代码复杂度低的效果。这个指针数组按照块大小实际包含4层,1024,2048,4096和8192,每一层基结点只管理同类大小的块。

关于bufpool部分的整体概述,暂时告一段落,后续还会陆续添加新的内容,并作进一步整理。老刘会坚持把这个分析一直做下去。如有兴趣的朋友也可以QQ加本人私聊:275787374,随时欢迎。

下一部分会将LRU进行详解。如发现文章中的任何错误欢迎朋友们指正。

时间: 2024-10-19 11:54:53

原创mysql内核源代码深度解析 缓冲池 buffer pool 整体概述的相关文章

SpringMVC 源代码深度解析&lt;context:component-scan&gt;(扫描和注册的注解Bean)

我们在SpringMVC开发项目中,有的用注解和XML配置Bean,这两种都各有自己的优势,数据源配置比较经常用XML配置,控制层依赖的service比较经常用注解等(在部署时比较不会改变的),我们经常比较常用的注解有@Component是通用标注,@Controller标注web控制器,@Service标注Servicec层的服务,@Respository标注DAO层的数据访问.SpringMVC启动时怎么被自动扫描然后解析并注册到Bean工厂中去(放到DefaultListableBeanF

MySql 缓冲池(buffer pool) 转

应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库. 操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问. MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO. 今天,和大家聊一聊InnoDB的缓冲池. InnoDB的缓冲池缓存什么?有什么用? 缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用. 速度快,

MySql 缓冲池(buffer pool) 和 写缓存(change buffer) 转

应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库. 操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问. MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO. 今天,和大家聊一聊InnoDB的缓冲池. InnoDB的缓冲池缓存什么?有什么用? 缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用. 速度快,

【大白话系统】MySQL 学习总结 之 缓冲池(Buffer Pool) 的设计原理和管理机制

一.缓冲池(Buffer Pool)的地位 在<MySQL 学习总结 之 InnoDB 存储引擎的架构设计>中,我们就讲到,缓冲池是 InnoDB 存储引擎中最重要的组件.因为为了提高 MySQL 的并发性能,使用到的数据都会缓存在缓冲池中,然后所有的增删改查操作都将在缓冲池中执行. 通过这种方式,保证每个更新请求,尽量就是只更新内存,然后往磁盘顺序写日志文件. 更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是比较高的,因为顺序写磁盘文件,他的性能要远高于随机读写磁盘文件. 正因为

【大白话系列】MySQL 学习总结 之 缓冲池(Buffer Pool) 如何支撑高并发和动态调整

如果大家对我的 [大白话系列]MySQL 学习总结系列 感兴趣的话,可以点击关注一波. 一.上节回顾 在上节< 缓冲池(Buffer Pool) 的设计原理和管理机制>中,介绍了缓冲池整体的设计原理.包括几个比较重要的概念:free 链表.flush 链表和 lru 链表.正式因为这一套机制,使得 InnoDB 存储引擎可以基于内存操作,避免了磁盘随机读写的低性能. 二.Buffer Pool 如何应对高并发场景 1.单个 Buffer Pool 的问题 直到现在,估计大家都以为缓冲池只是一个

腾讯云数据库团队:浅谈如何对MySQL内核进行深度优化

作者介绍:简怀兵,腾讯云数据库团队高级工程师,负责腾讯云CDB内核及基础设施建设:先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利:从事MySQL内核开发工作8年,具有丰富的优化经验:在分布式存储等领域有较丰富经验. MYSQL数据库适用场景广泛,相较于Oracle.DB2性价比更高,Web网站.日志系统.数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务型).存在多个分支.读写效

集群技术(三)MySQL集群深度解析

什么是MySQL集群 MySQL集群是一个无共享的(shared-nothing).分布式节点架构的存储方案,其目的是提供容错性和高性能. 数据更新使用读已提交隔离级别(read-committedisolation)来保证所有节点数据的一致性,使用两阶段提交机制(two-phasedcommit)保证所有节点都有相同的数据(如果任何一个写操作失败,则更新失败). 无共享的对等节点使得某台服务器上的更新操作在其他服务器上立即可见.传播更新使用一种复杂的通信机制,这一机制专用来提供跨网络的高吞吐量

SpringMVC 源代码深度解析 IOC容器(Bean实例化和依赖注入)

SpringMVC最核心的IOC的控制反转,动态的向某个对象提供它所需要的其他对象,例如:对象A时,需要对象B时,这时不像以前我们之前要在A对象里实例化B对象,这时B对象的实例化由IOC容器会主动创建一个对象B然后注入到对象A里,提供使用.我们项目开发中,最经常用到,那怎么实现实例Bean并依赖注入呢?我们今天带着这些问题来通过SpringMVC源代码进行深入的解析.这篇介绍不对注解实例化和注入进行讲解,这个放在后面在介绍. 我们平常写的一个类,并依赖调用了类的某个方法,这时需要依赖那个类已经实

[WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建

看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的细节比如字节码是如何生成的等等,为此成文. JSC对JavaScript的处理,其实与Webkit对CSS的处理许多地方是类似的,它这么几个部分: (1)词法分析->出来词语(Token): (2)语法分析->出来抽象语法树(AST:Abstract Syntax Tree): (3)遍历抽象语法