Innodb的缓冲池主要是用来存储访问过的数据页的,它就是一块连续的内存,通过一定的算法使这块内存得到有效的管理。它是数据库系统中拥有最大块内存的系统。Innodb存储引擎中数据的访问是按照页(默认为16K)的方式从数据库文件中读取到缓冲区中,然后在内存中用同样大小的内存空间来做一个映射,为了提高数据访问效率,数据库系统预先就分配很多这样的空间,用来与文件中的数据进行交换。访问时按照最近最少使用(LRU)算法来实现缓冲区页面的管理,经常访问的在最前面,最不经常的在最后面,如果缓冲区中没有空闲的页面来做文件数据的映射时,就从缓冲池中找到最后面的且不使用的将它淘汰然后拿来映射新数据文件页面,同时将它移到LRU链表中最前面。这样就能保证经常访问的页面在没有刷盘的情况下始终在缓冲池中,从而保证了数据库的访问效率。

缓冲池大小是可以配置的,可以通过配置文件中的参数innodb_buffer_pool_size的大小来决定,默认大小为 128M 。这个值一旦MYSQL已经启动,则是不能再做修改的,如果需要修改,只能退出MYSQL然后修改对应的my.ini来设置新的buffer大小,然后启动才能生效。

MySQL在启动时,如果存储引擎为innodb,则innodb会初始化其所有的子系统,其中就包括了页面缓冲区子系统,通过函数buf_pool_init来实现。Buffer缓冲池可以有多个实例,可以通过配置文件中的参数innodb_buffer_pool_instances来设置,默认值为1,实现多实例的缓冲池主要是为了提高在数据页放问时的并发度。每个实例的空间大小都是相同的,也就是说系统会将整个配置的缓冲区大小按实例个数平分,然后每个实例各自进行初始化操作。

一个缓冲区实例在代码中用buf_pool_t结构体来描述,这个结构体是用来管理一个缓冲区实例的核心工具,它里面包括了很多信息,主要包括上面提到的LRU链表,用来管理这个实例中所有页面的访问情况;FREE链表,用来存储这个实例中所有空闲的页面;flush_list链表,用来存储所有被修改过且需要刷到文件中的页面;mutex,这个主要用来保护这个缓冲池实例的,因为一个实例只能由一个线程访问;chunks,这个是一个指向这个实例第一个真正内存页面的首地址,所以页面都是连续存储,所以通过这个指针直接就可以访问所有的其它页面。

初始化一个缓冲池实例的内存空间的函数是buf_chunk_init,一个缓冲池实例的内存分布是一块连续的内存空间,这块内存空间中存储了两部分内容,前面是这些数据缓存页面的控制头结构信息(buf_block_t结构),每一个页面对应一个控制头信息,所以控制头信息连续存储在一起,所以控制信息存储完成之后才是真正的缓冲页面,所以一个缓冲池实例实际所用的空间是比配置中指定的要大,因为还需要存储控制头信息的空间,下面表示的是一个缓冲池实例的内存分布情况:

MySQL源码:Innobase缓冲池_第1张图片

       对于缓冲池中的所有页面,都有一个控制头信息与它对应,从上图可以看出,每一个ctl都控表示了一个属于自己的page的使用情况。初始化实例时当然还需要对每一个控制头信息进行初始化,也就是每一个buf_block_t结构,初始化一个页面控制信息是通过buf_block_init函数实现的,buf_block_t结构中包含了很多信息,主要包括:其对应的页面地址frame;页信息结构buf_page_t,这个结构用来描述一个页面的信息,包括所属表空间的ID号、页面号、被修改所使用的LSNnewest_modificationoldest_modification)、使用状态(现在共有9种状态)等;用来保护这个页面的互斥量mutex;访问页面时对这个页面上的锁lockread/write)等。在初始化完每一个页面之后,需要将每一个页面加入到上面提到的空闲页链表中,因为这些页面现在的状态都是未使用(BUF_BLOCK_NOT_USED)。

       到现在为止,缓冲池的一个实例就算初始化完成,在访问数据库的时候会通过这些内存页面来缓存文件数据。由于缓冲池涉及的内容比较多,比如MTR,这里只介绍了缓冲池,其它部分会一一在后面做详细介绍。