MySQL数据库InnoDB存储引擎 innodb_buffer_pool_size的Buffer Pool页面管理

Buffer Pool页面管理

Page to Buffer Pool

在MySQL 5.5及最新的版本中,InnoDB存储引擎支持多Buffer Pool Instance,内存多个buffer pool管理,此时指定一个page,到底是由内存中哪一个buffer pool来管理呢?这个实现很简单,看下面的函数分析:

// 给定一个page的Tablespace id与page no,找到对应的管理buffer pool

buf0buf.ic::buf_pool_get(space_id, page_no)

// 将page_no右移6位,去除低6位。因为目前InnoDB的一个read ahead是64个page

// 因此右移6位能够保证一个read ahead area中的页,都属于同一个buffer pool管理

ignored_offset = page_no>> 6;

// 根据space_id与ignored_offset计算fold,并从fold计算出对应的buffer pool index

fold = space_id<< 20 + space_id + ignored_offset;

index = fold % srv_buf_pool_instances;

// Page to Buffer Pool映射计算完毕

 

Buffer Pool LRU List

MySQL数据库InnoDB存储引擎的 Buffer Pool通过LRU算法管理页面的替换策略。LRU List按照功能被划分为两部分:LRU_young与LRU_old,默认情况下,LRU_old为链表长度的3/8。页面读取时(get/read ahead),首先链入LRU_old的头部。页面第一次访问时(read/write),从LRU_old链表移动到LRU_young的头部(整个LRU链表头)。

 

全表扫描的所有页面,也遵循先读入LRU_old,后移动到LRU_young的原则,会导致Buffer Pool中的其他页面被替换出内存。为防止全表扫描的负面影响,InnoDB存储引擎提供了系统参数,innodb_old_blocks_time:只有当页面的后续访问与第一次访问时间间隔大于此值,才会移动到LRU链表头。innodb_old_blocks_time在5.1.41版本中引入。默认为0,也就是说全表扫描的页面会进入LRU_young(链表头),一个大表的全表扫描会导致大量page被替换出内存

 

page movement in LRU

old page move to new?

page每次被访问时,都会调用buf0buf.cc::buf_page_set_accessed_make_young函数(通过adaptive hash index定位的索引页面,调用的是buf_page_make_young函数,但是同样会首先调用buf_page_peek_if_too_old来判断是否需要move page to new),此函数的功能,就是将位于LRU_old中的page;或者是位于LRU_young,但是进入LRU_young时间较长的page,将这些page移动到LRU的链表头,降低page被evict出内存的概率。移动的操作很简单,就是将page从原来的LRU链表中摘除(buf_LRU_remove_block),并且插入的LRU的链表头即可(buf_LRU_add_block_low)。在将page插入LRU链表头时,需要将当前buffer pool中的freed_page_clock(记录当前buffer pool中一共有多少page被evict到外存)拷贝到page结构中(用于标识当前page被插入LRU链表头时,buffer pool替换的页面数量的一个快照)。

 

与此同时,若页面第一次被访问,该函数还需要设置页面第一次被访问的时间:bpage->access_time.

 

buf0buf.ic::buf_page_peek_if_too_old()

// 1. 若当前buf pool没有evict任何page,说明buf pool足够大,

//      page能够完全放在内存。因此不需要将page从LRU_old移动到LRU_head

if (buf_pool->freed_page_clock == 0)

// 2. 若当前page不是第一次访问,同时用户设置了innodb_old_blocks_time参数,

//      那么计算本次访问与第一次访问的时间间隔是否超过了此参数设置,若未超过,

//      则本次访问不会将page从LRU_old移动到LRU_head

//     bpage->access_time:buffer pool中的page,第一次被访问的时间

if (buf_LRU_old_threshold_ms&&bpage->old)

access_time = buf_page_is_accessed();

if (access_time> 0 &&

ut_time_ms() –access_time>= buf_LRU_threshold_ms)

// 3. 以下的公式,用于判断当前page是否足够新,属于MRU,而不需要移动

//      buf_pool->freed_page_clock:       本buf pool一共evict了多少page

//      bpage->freed_page_clock:            本page最后一次移动到buf page LRU_head时,

//                                                                       bufpool当时的freed_page_clock取值

//      buf_pool->curr_size:                       buf pool当前使用的页面数量

//      BUF_LRU_OLD_RATIO_DIV:                  buf_pool->LRU_old_ratio的分母,取值为1024

//      buf_pool->LRU_old_ratio:              buf pool old LRU的占比,以1024为分母。例如:

//                                                                       默认3/8的情况下,buf_pool->LRU_old_ratio = 378

//      公式意义解释:

//      若从page上一次移动到buf pool的LRU_head以来,buf pool在此期间evict的page

//      数量,超过buf poolLRU_young list的长度的1/4,那么说明本page已经不够年轻,

//      本次访问需要移动page到LRU_head;否则说明page属于MRU,不需要移动page

buf_page_peek_if_young();

                   return((buf_pool->freed_page_clock & ((1UL << 31) - 1))

              < ((ulint) bpage->freed_page_clock

              + (buf_pool->curr_size

              * (BUF_LRU_OLD_RATIO_DIV - buf_pool->LRU_old_ratio)

              / (BUF_LRU_OLD_RATIO_DIV * 4))));

 

new page became old?

前面提到,每次页面被访问时,都可能导致页面在LRU list中的移动,移动到LRU list的头部(make young)。随着越来越多的page慢慢加入LRU list头,原来在LRU list中的page,必定慢慢退化,直至退化到LRU list的LRU_old list部分(3/8)。那么在哪些情况下,LRU list中的page会变为old?

 

经过对于MySQL数据库InnoDB存储引擎源码的调研,主要在以下功能模块中,一个LRU list中的page,会变为old page (bpage->old = true)。

 

  1. page被第一次读入buf pool时,会加入buf pool LRU list,并且设置为old

 

// old参数被设置为TRUE,加入LRU list LRU_old的头部

buf_page_init_for_read -> buf_LRU_add_block(bpage, TRUE);

// 若当前LRU list长度小于BUF_LRU_OLD_MIN_LEN = 512,那么page一定

// 加入到LRU list的头部。LRU list太短,无需区分LRU_young, LRU_old.

// 若指定的old为false,则也将page加入到LRU list的头部

// 否则,将page加入到LRU_old链表头的下一项

UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN;

// 若加入当前page之后,LRU链表长度达到BUF_LRU_OLD_MIN_LEN设置,

// 则重新初始化LRU链表为LRU_young与LRU_old两部分(原来不做区分)

// 初始化LRU_old的算法如下:

// 1. 首先将LRU list中所有的page均设置为old,长度为512

// 2. 调用buf_LRU_old_adjust_len函数调整LRU链表,重新计算LRU_young

//      与LRU_old部分的占比。

// 3. 由于LRU_old的占比默认为3/8,因此计算新的LRU_old部分的长度为189

//      将LRU_old头指针,从LRU链表头开始向后移动,直至LRU_old部分的长度

//      <= 189 + 20(BUF_LRU_OLD_TOLERANCE)为止。其中:

//      BUF_LRU_OLD_TOLERANCE参数指定了LRU_old部分长度可以偏离正常长度

//      的振幅。指定此参数,可以减少LRU链表重整的次数,提高LRU链表性能。

buf_LRU_old_init();

buf_LRU_old_adjust_len();

// 若LRU链表长度超过BUF_LRU_OLD_MIN_LEN设置,则每一个page的加入

// 都会引起LRU list的调整,重整算法与前面的步骤一致。

buf_LRU_old_adjust_len();

 

  1. page后续被访问时,第一次访问会被提升到LRU list head,后续访问也可能提升。随着page被提升,原有LRU list 中LRU_young部分的page,就会慢慢过期,变为LRU_old。

 

// 新访问的page被提升到LRU链表的头部,导致LRU_young部分越来越长,当

// LRU_young部分长度超过LRU链表长度的5/8时,就需要调整LRU链表,将LRU_old

// 指针沿着LRU链表向前移动,将LRU_young尾部的page,变为LRU_old

buf_page_get_gen() -> buf_page_set_accessed_make_young -> buf_LRU_old_adjust_len()

LRU_old = UT_LIST_GET_PREV(LRU, LRU_old);

buf_page_set_old(LRU_old, TRUE);

 

  1. 通过LRU list flush的dirty page,被移动到LRU链表的尾部,可以立即释放,交给用户线程分配free page。但是通过Flush list flush的dirty page,并不会移动page在LRU 链表中的位置。

LRU list flush,flush的是LRU 链表尾部的dirty page,本身就是属于最近不经常访问,因此可以释放。而Flush list flush,对应的page可能是最近经常访问的,因此不能移动。

// 当flsuh操作完成之后,判断当前的flush类型,若为LRU FLUSH,则将对应的page

// 移动到LRU链表的尾部(LRU_old部分的尾部,可以立即分配给用户)

buf_flush_write_complete();

if (flush_type == BUF_FLUSH_LRU)

buf_LRU_make_block_old();

 

推荐阅读的关联文章:
MySQL数据库分布式事务XA的实现原理分析

MySQL数据库InnoDB存储引擎Log漫游(1)
MySQL数据库InnoDB存储引擎Log漫游(2)
MySQL数据库InnoDB存储引擎Log漫游(3)

MySQL数据库InnoDB存储引擎原生Checkpoint策略及各版本优化详解

MySQL5.5数据库innodb_change_buffering怪异问题分析

MySQL数据库InnoDB存储引擎中的锁机制

MySQL数据库InnoDB存储引擎多版本控制(MVCC)实现原理分析

MySQL数据库分布式事务XA的实现原理分析

MySQL数据库分布式事务XA的实现原理分析

MySQL数据库分布式事务XA优缺点与改进方案

MySQL数据库InnoDB存储引擎 innodb_buffer_pool_size初始化详解

MySQL数据库InnoDB存储引擎 Buffer Pool页面分配详解

2 thoughts on “MySQL数据库InnoDB存储引擎 innodb_buffer_pool_size的Buffer Pool页面管理

  1. Pingback: MySQL数据库InnoDB存储引擎 Buffer Pool页面分配详解 | MySQLOPS 数据库与运维自动化技术分享

  2. Pingback: MySQL数据库InnoDB存储引擎源码解读 Buffer Pool Flush List详解 | MySQLOPS 数据库与运维自动化技术分享

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>