MySQL数据库InnoDB存储引擎 Buffer pool LRU List Flush策略详解
buffer pool LRU list flush(innodb_buffer_pool_size)
每当一个新页面读取buf pool之后,MySQL数据库InnoDB存储引擎都会判断当前buf pool的free page是否足够,若不足,则尝试flush LRU链表。
在MySQL 5.6.2版本中,MySQL数据库InnoDB存储引擎引入了page_cleaner线程,将遍历LRU链表,flush dirty page,释放free page的重任,从用户线程中剥离出来。page cleaner线程1s运行一次。
本小节的以下部分,将分别对MySQL 5.6.2以前的LRU list flush策略,以及page cleaner线程的LRU list flush策略,做详细的分析。
before MySQL 5.6.2
在MySQL 5.6.2之前,用户线程在读入一个page (buf_read_page)、新建一个page(buf_page_create)、预读page(buf_read_ahead_linear) 等等操作时,都会在操作成功之后,调用buf_flush_free_margin函数,判断当前buf pool是否有足够的free pages,若free pages不足,则进行LRU list flush,释放出足够的free pages,保证系统的可用性。接下来我们可以看看buf_flush_free_margin函数究竟是如何处理的?
Buf0flu.c::buf_flush_free_margin()
// 判断当前buf pool中需要flush多少dirty pages,才能够预留出足够的可被替换的
// 页面(free pages or clean pages in LRU list tail)。可用pages由以下两部分组成:
// 1. buf pool free list中的所有page,都是可以立即使用的。
// 2. buf pool LRU list尾部(5+2*BUF_READ_AHEAD_AREA)所有的clean pages。
// 其中:BUF_READ_AHEAD_AREA为64,是一个linear read ahead读取的大小,1 extent
//
// 那么多少可用的pages才是足够的呢?InnoDB中的定义如下:
// BUF_FLUSH_FREE_BLOCK_MARGIN() + BUF_FLUSH_EXTRA_MARGIN()
// 1. BUF_FLUSH_FREE_BLOCK_MARGIN = 5 + BUF_READ_AHEAD_AREA = 5 + 64 = 69
// 2. BUF_FLUSH_EXTRA_MARGIN = ((BUF_FLUSH_FREE_BLOCK_MARGIN / 4 + 100) /
// srv_buf_pool_instance)
// extra_margin的取值,为127除以系统中buf pool的个数
// 因此,对于1个instance的InnoDB系统,可用的个数约为:69 + 127 = 196
//
n_to_flush = buf_flush_LRU_recommendation();
// 读取buf pool free list中的页面个数
n_replaceable = UT_LIST_GET_LEN(buf_pool->free);
// 从buf pool LRU list tail开始向前遍历BUF_LRU_FREE_SEARCH_LEN(5+2*64)个
// pages,若page可以被replace,则增加n_replaceable取值
// LRU中的page,可以replace的条件,可见前面申请新空闲页中的分析。简单
// 来说,page一定是clean的;而且page没有被其他线程pin住,就可以replace
if (buf_flush_ready_for_replace())
n_replaceable++;
// 计算出n_replaceable取值之后,InnoDB还做了一个小小的优化
// 由于此时的flush LRU list是在用户线程中直接进行的,会影响到用户的响应时间
// 因此做了一个判断:若n_replaceable大于BUF_FLUSH_FREE_BLOCK_MARGIN
// 那么就不需要开始一次LRU flush;否则,返回需要flush的LRU list中的pages
// 个数为n_to_flushed
if (n_replaceable >= BUF_FLUSH_FREE_BLOCK_MARGIN)
return 0;
n_to_flush = BUF_FLUSH_FREE_BLOCK_MARGIN + BUF_FLUSH_EXTRA_MARGIN
- n_replaceable;
retrun n_to_flush;
// 若函数buf_flush_LRU_recommendation返回值大于0,说明需要进行LRU list flush
// 则调用buf_flush_LRU函数,进行LRU list flush。
// 由于buf_flush_LRU在MySQL 5.6.2之前与之后的版本中并无太大区别,因此将在
// 后面集中给于分析。
if (n_to_flush > 0)
buf_flush_LRU();
由于buf_flush_free_margin函数是在用户线程中调用执行的,若需要flush LRU list,那么对于用户的响应时间有较大的影响。因此,在MySQL 5.6.2之后,InnoDB专门开辟了一个page cleaner线程,处理dirty page的flush动作(包括LRU list flush与flush list flush),降低page flush对于用户的影响。在接下来的一小节,我们来看看page cleaner线程是如何处理每次的LRU list flush的。
after MySQL 5.6.2
在MySQL 5.6.2之前,buf pool LRU list的flush,是在用户线程中完成的,会影响用户的响应时间,为了解决此问题,MySQL 5.6.2版本,MySQL数据库InnoDB存储引擎在后台新增了一个page cleaner线程,专门用于处理dirty page的flush (包括LRU list与flush list)。接下来我们来看看page cleaner线程是如何处理的?
srv0start.cc::innobase_start_or_create_for_mysql();
// 在InnoDB启动的主流程中,创建page cleaner线程
os_thread_create(buf_flush_page_cleaner_thread, NULL, NULL);
while
// 如果当前系统处于活跃状态,则page cleaner线程每次休眠1s
if (srv_check_activity(last_activity))
page_cleaner_sleep_if_needed();
next_loop_time = ut_time_ms() + 1000;
// flush LRU list,在MySQL 5.6.6中,LRU list flush,只能由page cleaner线程发起
// 1. InnoDB新增了一个系统参数,innodb_lru_scan_depth,用于控制每次LRU lsit
// flush需要扫描的LRU tail链表的长度,默认取值为1024
// 2. 于此同时,一个LRU list flush操作,被拆分为每次100个chunk大小的操作
// 保证能够及早的释放出free page,用户线程不需要做长时间的等待。
page_cleaner_flush_LRU_tail();
for (j = 0; j < srv_LRU_scan_depth; j += PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE)
buf_flush_LRU(PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE);
// 进行完LRU list的flush之后,再进行flush list的flush,将在以下的章节分析
page_cleaner_flush_pages_if_needed();
buf_flush_LRU
在MySQL 5.6.2前后的版本中,LRU list flush的不同之处在于是由用户线程发起,还是有后台page cleaner线程发起。但是,无论是用户线程,还是后台page cleaner线程,再决定需要进行LRU list flush之后,都会调用buf_flush_LRU函数进行真正的flush操作。
不同之处在于,MySQL 5.6.2之前,用户线程调用的buf_flush_free_margin函数,在判断是否真正需要进行LRU list flush时,将LRU list tail部分的clean pages也归为可以被replace的pages,不需要flush。而在page cleaner线程中,每隔1s,无论如何都会进行一次LRU list flush调用,无论LRU list tail中的page是否clean。这也可以理解,用户线程,需要尽量降低flush的概率,提高用户响应;而后台线程,尽量进行flush尝试,释放足够的free pages,保证用户线程不会堵塞。
接下来详细分析下buf_flush_LRU函数是如何实现的 (主要基于MySQL 5.6.6 labs版本的实现,对比了MySQL 5.5.16版本,有部分差别)?
- 初始化BUF_FLUSH_LRU类型的flush (buf_flush_start()).
- 开始进行BUF_FLUSH_LRU类型的flush (buf_flush_batch). 首先获得buf pool的mutex,在整个LRU list flush过程中,buf pool mutex一直持有。因此flush过程中,用户线程是无法分配新页面的,这也是为什么在前面,LRU list flush要被划分为一个个小chunk的原因所在。
- 首先尝试从buf pool的unzip LRU中释放free pages (MySQL 5.6.6版本新增)。若最终释放的空间不足,则继续调用buf_flush_LRU_list_batch函数,flush common LRU list。
- 从LRU list尾部向前遍历,对于读取到的page,判断page是否属于clean page并且未被pin住,若是,则直接调用buf_LRU_free_block函数释放当前page,从LRU链表中删除,插入free链表。
- 若page不是clean page,则调用buf_flush_page_and_try_neighbors函数flush当前page (与前面Buffer Pool页面分配章节不同,在那个章节,只flush一个page,调用的函数是buf_flush_page)。
后台LRU flush,在flush当前页面的同时,也会尝试对当前页面的neighbors进行flush (neighbors的定义为:当前page所属的read ahead area属于同一neighbors,64个pages,一个extent)。遍历当前页面所属的extent,尝试对其中的每一个page进行flush。在LRU list flush中,可以被flush的neighbor page必须满足两个条件:
l neighbor page必须是old page (bpage->old);疑问:bpage->old何时被设置?
l neighbor page必须未被pin在内存中,未被使用,当前LRU flush线程能够获取page上的latch,而不需要等待。
满足以上两个条件的neighbor page,就可以调用buf_flush_page函数进行真正的flush了。关于此函数的处理流程,可见本文上面的分析。
- 在完成LRU list flush之后(buf_do_LRU_batch函数返回),释放buf pool mutex,然后调用buf_dblwr_flush_buffered_writes函数(在MySQL 5.5.16版本中,这个函数是buf_flush_buffered_writes)将DoubleWrite buffer flush到磁盘上,然后将DoubleWrite buffer对应的dirty page写出到正确的位置(sync)。sync的过程中,会发出signal通知write thread,write thread收到写成功的消息之后,会调用buf_page_io_complete函数,完成dirty page flush的最后清理操作(本文前面已经提到,需要清理的包括从flush list中删除,清除page的oldest_modification标识等等)。
- 最后,与步骤1对应,将步骤1初始化的BUF_FLUSH_LRU标识清理(buf_flush_end),然后记录相应的统计信息(buf_flush_common)。完成这些操作之后,一次buf_flush_LRU操作结束。
建议继续学习:
- Buffer和cache的区别是什么? (阅读:6840)
- Linux操作系统中内存buffer和cache的区别 (阅读:5320)
- 快速预热Innodb Buffer Pool的方法 (阅读:3989)
- MySQL数据库InnoDB存储引擎 Insert Buffer实现机制详解 (阅读:3545)
- InnoDB之Dirty Page、Redo log (阅读:3445)
- grep 命令的buffer选项 (阅读:3073)
- 小心grep 的buffer (阅读:3024)
- ConcurrentHaspLRUHashMap实现初探 (阅读:2807)
- HBase如何合理设置客户端Write Buffer (阅读:2729)
- 浅谈数据库系统中的cache (阅读:2464)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:hedengcheng 来源: MySQLOPS 数据库与运维自动化技术分享
- 标签: Buffer LRU
- 发布时间:2012-06-20 00:03:19
- [45] 界面设计速成
- [42] Oracle MTS模式下 进程地址与会话信
- [41] 图书馆的世界纪录
- [41] 如何拿下简短的域名
- [41] android 开发入门
- [40] IOS安全–浅谈关于IOS加固的几种方法
- [39] 视觉调整-设计师 vs. 逻辑
- [37] 程序员技术练级攻略
- [37] 【社会化设计】自我(self)部分――欢迎区
- [36] 读书笔记-壹百度:百度十年千倍的29条法则