5月11-13日在北京国际会议中心举行数据库大会,有幸得友人推荐在大会上讲了一场。源于自己曾经参加一些技术大会的感受------抱着学习的目的,非常兴奋非常饥渴的过去了,但往往也是相当饥渴的回来了,并不是老师分享的内容没有营养跟价值,而往往是老师讲得内容太高大上,太丰富,营养价值过高,难以在短短的一个小时内吸收消化,所以依然是饥饿的 。基于这样的感受,所以作者在这次大会分享一个“接地气"的内容,心想从事mysql运维或者使用mysql的朋友,或多或少的都想知道数据库内部的运行机制,以及更想知道数据库参数该如何设置,才能使数据库的性能得到最大的发挥,所以以”MySQL核心参数含义的源码解析"为题目,来进行分享,希望让听讲的朋友们对mysql参数有更深入的理解。
目地是美好的,但效果却很骨感的。为了抓紧这短暂40~50分钟的时间,中间毫不间断的,差不多以作者最快的语速,侧着身拿着荧光笔,不停在这大屏幕上一边笔画,一边讲解,目的是希望在有限的时间内把所有必要的细节都讲到,以至忘了这是一场演讲,而不是真正地当老师在讲课,杯具。。。。。。
当我以最快的速度在规定的时间把内容讲完,台下的所认识的朋友跟我反馈说,前面一半跟上了,后面稍微没留神,没有跟上,后面就完全听不懂了。太失落了,跟我想的效果完全不一样啊,我讲得够详细了,只不过语速快了点。但令作者还有些安慰的是:有陌生的听众在分享后当场跑过来跟我反馈说,你分享的内容真的很好,连代码的细节都讲到了,非常受益。兄弟,跪谢了,总算还有人懂我。
既然是分享,目的是让更多的希望收获知识的朋友得到分享的内容。作者现将ppt的内容,以及解析的内容以文子的形式描述出来,希望让没参加大会的朋友,也能比较容易的了解这次分享的内容。同时,也能够让在大会上没有听明白的朋友继续补习。坚守到最后一场,听作者演讲,没有半点收获,作者岂不是愧对你们了。 再次谢谢坚持下来听我分享的朋友。
下面是分享后整理的实录内容:
大家下午好,今天我分享的主题是"mysql核心参数含义的源码解析“。mysql的参数非常多,鉴于今天下午时间的关系,我只会讲其中一部分参数,这部分参数是关于buffer pool . 我们通过解析buffer pool(缓存池)的源代码,来直观地了解这些参数的真正含义。
这个是今天要讲的内容:我们首先简单介绍buffer pool的工作机制,然后去解析buffer pool的核心函数。(讲完后,因为看到上面的目录,有听众问我是不是写了这本书,要问我买。其实我只是借用了word生成目录的功能。)
在正式讲今天的内容之前,我们来简单聊一下,在mysql日常运维过程中,我们所经历的一些困惑。例如有时候数据库莫名其妙的变慢了,通常(或者可能)在10分钟前还是好好的,现在却出现了问题。在我们进行诊断的时候,可能会发现cpu,或者io , memory等出现了一些状况。甚至有个时候,将这些指标跟10分钟之前比较,看不出任何异常。
假如有幸我们发现了一些异常现象,但这些异常的现象,是产生问题本身的原因还是问题出现后表现出来的现象?仅仅从现象本身来看,是不太好定位问题产生的原因的。
在遇到一些不好定位的问题之后,跟同事讨论后,但又各自可能有不同的意见,不同的观点,这通常是我们最大的困惑------问题没有解决,而且对问题背后的原因没有定论,接下来不知道该如何处理?但计算机世界是一个客观的世界,不存在主观性,问题背后的原因一定是确定的,虽然有可能是多种因数在特定条件下综合在一起的结果。这类问题往往是最难定位的,从片面地维度来寻找答案都是失真的,错误的。所以,在这个时候,当我们对数据库内部越来越了解,了解得越来越全面,定位这类问题就会越来越准确,越来越接近问题的本质。下面就让我们来一起对mysql的知识进行深入的了解。
下面我们正式来讲今天的内容: mysql的参数非常多,我简单地列出一部分并做一下简单的分类。
跟事务安全/事务提交相关的参数,例如最著名innodb_flush_log_at_trx_commit, sync_binlog 。
跟各种类型cache相关的参数,例如thread_cache_size,
table_open_cache。
其他重要的参数,例如跟并发控制相关的参数,例如max_connection, innodb_thread_concurrency. 我们今天要讲这部分参数,就是buffer pool(缓存池)相关的, 这里提出了多个关于buffer pool的参数,不知大家对这些参数的含义是否有比较清晰的、深入的理解? 如果绝大部分朋友对这些参数尚未完全了解,那很好。 我们今天下午的分享就比较有价值 。后面会对buffer pool源码进行解析,会比较清楚地介绍到这些参数在哪些函数中被使用到,通过了解这些函数的功能跟实现,也就能直观地了解这些参数的含义。
我们先来简单地看一下buffer pool的工作机制。根据我的理解,buffer pool两个最主要的功能:一个是加速读,一个是加速写。加速读呢? 就是当需要访问一个数据页面的时候,如果这个页面已经在缓存池中,那么就不再需要访问磁盘,直接从缓冲池中就能获取这个页面的内容。加速写呢?就是当需要修改一个页面的时候,先将这个页面在缓冲池中进行修改,记下相关的重做日志,这个页面的修改就算已经完成了。至于这个被修改的页面什么时候真正刷新到磁盘,这个是buffer pool后台刷新线程来完成的,后面会详细讲到。
在实现上面两个功能的同时,需要考虑客观条件的限制,因为机器的内存大小是有限的,所以mysql的innodb buffer pool的大小同样是有限的。在通常的情况下,当数据库的数据量比较大的时候,缓存池并不能缓存所有的数据页,所以也就可能会出现,当需要访问的某个页面时,该页面却不在缓存池中的情况,这个时候就需要从磁盘中将这个页面读出来,加载到缓存池中,然后再去访问。这样就涉及到随机的物理io,也就延长了访问页面所消耗的时间。
这样的情况是一个bad case,是我们期望尽量避免的------因此需要想办法来提高缓存的命中率。 innodb buffer pool采用经典的LRU列表算法来进行页面淘汰,以提高缓存命中率。将缓存的页面按照最近使用的次数跟时间进行排序,队列最末尾的页面将会最先被淘汰。这个机制在后面会结合源码详细讲解。同时,在LRU列表的中间位置打了一个old标识,可以简单的理解为将LRU列表分为两个部分,这个标记到LRU列表头部的页面称之为yong的页面,这个标志到LRU列表尾部的页面称之为old页面。再进行抽象的话,我们简单地理解为缓存池被分成两个池子,一个叫young池子,一个叫old池子。当一个页面从磁盘上加载缓存池中的时候,会将它排放在这个old标识之后的第一个位置,也就是说放在了old池子中。这个机制的作用就是,在做大表的一次性全表扫描的时候,大量新进来的页面,是存放在old池子中的,当old池子的大小不够缓存新进来的页面的时候,也只是在old池子中内部进行循环冲洗,这样就不会冲洗young池子中的热点页面,从而保护了热点页面。这就是LRU列表的机制。
另外,前面我们讲到页面更新是在缓存池中先进行的,所以需要考虑这些被修改的页面什么时候刷新到磁盘?以什么样的顺序刷新到磁盘?在innodb buffer pool中,采用的方式是将页面在缓存中的按照第一次修改时间,也就是变成脏页的时间进行排序,flush列表进行排序,由后台刷新线程依次刷新到磁盘,实现修改落地到磁盘。
我们简单介绍了buffer pool的工作机制,我们现在来看buffer pool里面最重要的三个列表,前面已经讲了两个列表,LRU列表以及flush列表,也就是脏页刷新列表。现在再补充一个列表------空闲列表。空闲列表中的内存块,是没有存放任何数据页的内存块。当没有在缓存池中的页面需要被访问时,它需要先被加载到缓存池中,从而需要从空闲列表中取出一个空闲内存块来缓存这个页面。
在这里提一下,一个bufferpool 可能会分成好几个buffer pool instance , 在mysql5.7中,如果不显示设置innodb_buffer_pool_instances这个参数,当innodb buffer size 大于1G的时候,就会默认会分成8个instances,如果小于1G,就只有1个instance。
下面我们来看一下一个数据页的访问流程。
1. 当访问的页面在缓存池中命中,则直接从缓冲池中访问该页面。
2. 如果没有命中,则需要将这个页面从磁盘上加载到缓存池中,因此需要在缓存池中的空闲列表中找一个空闲的内存块来缓存这个从磁盘读入的页面。
3. 但存在空闲内存块被使用完的情况,不保证一定有空闲的内存块。假如空闲列表为空,没有空闲的内存块,则需要想办法去产生空闲的内存块。
4. 首先去LRU列表中找可以替换的内存页面,查找方向是从列表的尾部开始找,如果找到可以替换的页面,将其从LRU列表中摘除,加入空闲列表,然后再去空闲列表中找空闲的内存块。这就是LRU列表中的页面淘汰机制。
5. 如果在LRU列表中没有找到可以替换的页,则在列表最末尾选择一个页面进行刷新,刷新后加入空闲列表,然后再去空闲列表中取空闲内存块。
因为空闲列表是一个公共的列表,所有的用户线程都可以使用,存在争用的情况。因此,自己产生的空闲内存块有可能会刚好被其他线程所使用,所以用户线程可能会重复执行上面的查找流程,直到找到空闲的内存块为止。
画图跟表述可能没有完全清楚地表达,下面我们来看一下查找空闲内存块的源代码:
这个函数的名称是buf_LRU_get_free_block,单纯从函数的命名来看,我们就能大概猜出这个函数的作用-------获取空闲的内存块。我们来解析这个函数:
1. 首先看函数的开头部分------我们直接看代码注释,ifthere is a block in the free list, take it .从空闲列表中去获取block, 如果获取到,就返回。 这个return, 是该函数的唯一返回出口,也就是一定要找到空闲的block才返回,否则一直循环找下去。
2. 如果没有找到,则从LRU列表的尾部开始找可以替换的BLOCK,第一次查找最多只扫描100个页面,循环进行到第二次时,会查找深度就是整个LRU列表。如果找到可以替换的页,则将其加入到空闲列表,然后再去空闲列表中找。
3. 如果在LRU列表中没有找到可以替换的页,则进行单页刷新, 将脏页刷新到磁盘之后,然后将释放的内存块加入到空闲列表。然后再去空闲列表中取。为什么只做单页刷新呢?因为这个函数的目的是获取空闲内存页,进行脏页刷新是不得已而为之,所以只会进行一个页面的刷新,目的是为了尽快的获取空闲内存块。
中间还有一些细节,包括设置刷新事件,以请求后台刷新线程进行脏页刷新,以及当进行第三次循环时,线程自己先sleep 10 毫秒,然后再去做页面刷新。这些将不再祥描。
通过了解了空闲页面的查找流程之后,我们知道,如果需要刷新脏页来产生空闲页面或者需要扫描整个LRU列表来产生空闲页面的时候,查找空闲内存块的时间就会延长,这个是一个base case,是我们希望尽量避免的。因此,innodb buffer pool 中存在大量可以替换的页面,或者free 列表中一直存在着空闲内存块,对快速获取到空闲内存块起决定性的作用。 在innodbbuffer pool的机制中,是采用何种方式来产生的空闲内存块,以及可以替换的内存页的呢?这就是我们下面要讲的内容------通过后台刷新机制来产生空闲的内存块以及可以替换的页面。
在讲innodb buffer pool的刷新机制之前,我们再来简单看一下有关buffer pool 的参数,这些参数将在后面的源码解析中使用到。
这就是我们接下来要讲的内容:这些函数是跟缓存池后台页面刷新相关的函数。后台刷新的动作由后台刷新协调线程触发,该线程的所有工作内容均由
buf_flush_page_cleaner_coordinator函数完成,我们后面简称它为协调函数。
其会调用page_cleaner_flush_pages_recommendation函数,我们后面简称它为建议函数或者推荐函数。在执行刷新之前,会用建议函数生成每个buffer pool需要刷新多少个脏页的建议。具体是怎么生成建议的呢?就是子目录的内容,后面会详细讲到。生成完刷新建议之后,其后就会产生请求刷新的事件,后台刷新线程在收到请求刷新的事件后,会执行pc_flush_slot函数对某个缓存池进行刷新,刷新的过程首先是对lru列表进行刷新,执行的函数为buf_flush_LRU_list,完成LRU列表的刷新之后,就会根据建议函数生成的建议对脏页列表进行刷新,执行的函数为buf_flush_do_batch。
所有的buffer pool 都已经开始刷新之后,就开始等待所有buffer pool刷新的完成,等待函数为pc_wait_finished.
上面介绍了协调函数的工作流程,下面我们就逐步来解析这个函数以及相关子函数。
这个协调函数的作用前面已经讲过,是进行刷新循环的调度的。稍微补充一下,它期望每秒钟对buffer pool 进行一次刷新调度。如果相邻两次刷新调度的间隔超过4000ms ,也就是4秒钟,mysql的错误日志中会记录相关信息,意思就是“本来预计1000ms的循环花费了超过4000ms的时间。
我们来看一下后台刷新协调函数的源代码:
左边是循环超时时,在错误日志记下相关信息。右边是调用对每个buffer pool 生成需要刷新多少脏页的建议函数。
接下来,我们来看后台刷新协调函数的主体流程。
1. 调用建议函数,对每个缓冲池实例生成脏页刷新数量的建议。
2. 生成刷新建议之后,通过设置事件的方式,向刷新线程发出刷新请求.
3. 后台刷新的协调线程会作为刷新调度总负责人的角色,它会确保每个buffer pool 都已经开始执行刷新。如果哪个buffer pool的刷新请求还没有被处理,则由刷新协调线程亲自刷新,且直到所有的buffer pool instance都已开始/进行了刷新,才退出这个while循环。
4. 当所有的buffer pool instance的刷新请求都已经开始处理之后,协调函数(或协调线程)就等待所有buffer pool instance的刷新的完成。如果这次刷新的总耗时超过4000ms,下次循环之前,会在数据库的错误日志记录相关的超时信息。
前面我们反复讲到,每个buffer pool 需要刷新多少页面是由建议函数生成的,它在做刷新建议的时候,具体考虑了哪些因素?现在我们来详细解析。
在讲这段内容之前,我们先来了解两个参数:
innodb_io_capacity与innodb_io_capacity_max,这两个参数大部分朋友都不陌生,设置这个参数的目的,是告诉mysql数据库,它所在服务器的磁盘的随机IO能力。mysql数据库目前还没有去自己评估服务器磁盘IO能力的功能,所以磁盘io能力大小由这个参数提供,以便让数据库知道磁盘的实际IO能力。这个参数将直接影响建议刷新的页面的数量。
我们来简单看一下推荐函数中的内容:
首先它会计算当前的脏页刷新平均速度以及重做日志的生成平均速度。但这个函数并不是每次被调用时,都计算一次平均速度。它是多久计算一次的呢?这个是由数据库参数
innodb_flushing_avg_loops 来决定的。 默认是30,当这个函数被调用了30次之后或者经过30秒之后,重新计算一次平均值。我们暂且简单理解为30秒钟。计算规则是当前的平均速度加上最近30秒钟期间的平均速度再除以2得出新的平均速度。两个平均值相加再平均,得出新的平均值。这样的平均值能明显的体现出最近30秒的速度的变化。
接下来,它会根据innodbbuffer pool的脏页百分比来计算innodb_io_capacity 的百分比. 然后会根据重做日志中的活跃日志量的大小,也就是lsn的age,占重做日志文件大小的百分比来计算innodb_io_capacity的百分比 . 将这两项计算结果进行比较,取大的值作为最终的innodb_io_capacity 的百分比,用变量pct_total 为保存。假如计算出来的得到pctl_total为90, 而数据库参数innodb_io_capacity设置为1000,则根据这两个因素再结合所设置的磁盘io能力,得出的建议就为刷新900个脏页。
然后,会根据前面计算重做日志的生成平均速度,来计算建议每个buffer pool instance 刷新多少脏页以及所有pool buffer的刷新总量。之所有会基于这个因素来考虑,我认为是这样的:新产生的重做日志是活跃的重做日志,根据活跃日志的生成速度来计算需要刷新的脏页的数量,从而将使活跃日志的过期速度跟生成速度达到一个均衡,这样控制了活跃的重做日志在一个正常的范围,保障了重做日志文件一直有可以使用的空间。在这里简单说明一下活跃的重做日志跟不活跃的重做日志的区别:活跃日志是指其记录的被修改的脏页还没有被刷新到磁盘,当mysql 实例crash之后,需要使用这些日志来做实例恢复。
再接下来,通过上面的计算,我们从不同维度分别得出三个建议刷新的数量:分别为当前的脏页刷新的平均速度,也就是一秒钟刷新了多少脏页;根据脏页百分比,以及活跃日志量的大小,以及所设置的innodb_io_capacity 参数所得出建议刷新的数量;以及根据重做日志产生速度计算得出的建议刷新数量。将这三个值相加之后再平均,得出的就是考虑了上面所有因素的一个综合建议,由变量n_pages保存。
接下来,这个建议刷新的总量n_pages会跟innodb_io_capacity_max这个参数进行比较,也就是建议刷新的总量最大不能超过所设置的磁盘最大随机io能力。
最后,生成最终的刷新建议。生成最终的刷新建议时,会考虑当前数据库的活跃日志量的大小,当前活跃日志比较少的时候,认为重做日志文件有足够可以使用的空间(以变量pct_for_lsn小于30为依据),则不需要考虑每个buffer pool 之间的脏页年龄分布不均的情况,每个buffer pool 刷新相同的数量,数量就刷新总量除以buffer pool的个数。如果活跃日志比较多(以变量pct_for_lsn大于等于30为依据),则需要考虑脏页的年龄在每个buffer pool的分布不同,每个buffer刷新不同的数量的脏页,老的脏页比较多的buffer pool instance刷新的数量也就多。
以上就是建议函数生成刷新建议时的计算流程,下面根据源码来分析如何具体考虑这些因素,以便让我们有非常直观的理解。
首先来计算平均值,前面已经有比较清楚的讲过,现在大家来简单地看一下这部分代码,主要请关注这个if条件:当循环次数达到innodb_flush_avg_loops时或者经历的时间达到该值时,才进行新的平均值的计算。因此,大家清楚了这个参数的含义,是用来指明隔多久计算一次平均值。平均值计算规则就是新平均速度=当前的平均速度+最近这段期间平均速度,再除以2 。
接下来这一段代码呢,是首先计算lsn的age, 也就是活跃日志量的大小,然后调用相关函数根据脏页百分比来计算io_capacity的百分比,用变量pct_for_dirty保存,然后根据活跃日志量的大小来计算io_capacity的百分比,用变量pct_for_lsn来保存,这个值后面会被是使用到,用来决定每个buffer pool是建议刷新相同的数量的脏页,还是刷新不同的数量。当pct_for_lsn<30的时候,建议每个buffer刷新相同数量的页面。否则,建议刷新不同数量的页面。
最后比较这两个变量的大小,大的值作为最终的io_capacity的百分比,用变量pct_total保存。接下来我们将来看看是如何具体跟据这两项来计算io_capacity的百分比的。
函数af_get_pct_for_dirty()的计算逻辑是:
首先获取缓存池的脏页百分比,然后根据这个值进行判断。
如果参数最大脏页百分比的低水位设置为0(默认值),当dirty_pct大于参数innodb_max_dirty_pages_pct,则返回100, 否则返回0。
如果设置了最大脏页百分比的低水位,当脏页百分比超过该值时,则返回相应的比例。当脏页百分比越接近最大脏页百分比,返回比例越接近100。 否则为0。
再来看看根据lsn的age,即活跃日志量来计算io_capacity百分比的规则。
如果活跃日志量占日志文件大小的百分比小于参数innodb_adaptive_flushing_lwm,即自适应刷新的低水位,默认是10,则直接返回0。
如果没有设置自适应刷新参数innodb_adaptive_flushing_lwm,默认为on ,则需要等待活跃的日志量大于max_async_age的值,才会返回相应的百分比,否则返回0。可以简单的理解为,如果没有开启自适应刷新,则必须等待活跃日志量的过大,大到存在危害数据库的可用性风险时,才开始考虑基于活跃日志量的大小来进行脏页刷新。
如果开启了自适应刷新,活跃日志量所占百分比大于自适应刷新的低水位时(innodb_adaptive_flushing_lwm),返回相应的百分比。具体计算公式查看ppt上的内容。
接下来,我们来看看是怎么根据重做日志的生成速度来计算每个buffer需要刷新多少脏页的。这一段代码,不涉及数据库的任何参数,代码的功能就是根据重做日志生产的速度,来计算每个buffer需要刷新多少页面以及所有buffer pool所建议刷新的总量,但这个不是最终的建议。
首先,根据前面计算得出的lsn_avg_rate,即重做日志产生的平均速度,计算出一个target_lsn号。
然后从每一个buffer pool的脏页列表的队尾开始取出脏页,将脏页的old_modifiaction(最小的lsn)跟target_lsn进行比较,这里简单的说明一下脏页的oldest_modification的含义,它表示的是脏页第一次修改时的lsn号,也就是脏页的最小lsn号。如果它小于target_lsn,然后将其作为刷新对象进行计数,否则,退出这个buffer pool 内的循环.因为刷新列表时按照脏页的最小lsn号进行排序的,前面的脏页的最小lsn都大于target_lsn ,所以不需要再继续找下去。
从上面的计算方式可以看出,当重做日志生成的平均速度越大,target_lsn 就越大,同时,如果buffer_pool中的脏页的old_modition小于target_lsn的数量越多,也就是老的脏页越多,被建议刷新的页面就越多。
这张ppt上一张ppt代码段的注释。
生成最终的刷新建议。
通过前面的计算,我们从不同维度分别得出三个建议刷新的数量,然后将这个三个值进行平均,得出了综合所有因素的一个刷新建议总量,由变量n_pages保存。
影响刷新总量的因素有:脏页的百分比,活跃日志量的大小,当前redo生成的平均速度,当前脏页刷新平均速度,以及脏页的age分布情况,以及参数innodb_io_capacity,innodb_io_capacity_max。
前面根据活跃日志量计算所得出的io_capacity的百分比的这个变量------pct_for_lsn,在这里再次被用到。当pct_for_lsn <30时,认为重做日志文件有足够的可用空间,不需要考虑脏页的年龄在buffer pool instance之间分布不均的情况,建议每个buffer刷新相同的数量,否则,需要考虑脏页的年龄分布情况,每个buffer pool instance所建议刷新的脏页数量不同,老的脏页比较多的buffer pool会被建议刷新更多地数量。
上面就是完整的刷新建议函数的解析,里面涉及到一些相关参数的使用,不知道大家对涉及到参数是否已经了解。
当生成刷新建议之后,就设置刷新请求事件,请求刷新线程进行脏页批量刷新。 函数pc_request特别简单。
1. 将所有bufferpool instances 的刷新状态设置为PAGE_CLEANER_STATE_REQUESTED,即申请刷新.
2. 通过设置事件,唤醒/触发page cleaner 线程调用pc_flush_slot函数来进行buffer pool的批量刷新。
Page_cleaner线程收到刷新请求之后,进行批量刷新。函数为pc_flush_slot.
寻找一个状态为申请刷新的缓存池实例,然后选为刷新对象,将状态修改为flushing.。然后执行后面的刷新。
执行buf_flush_LRU_list函数进行LRU列表的刷新,
3. 执行buf_flush_do_batch批量刷新脏页列表,该buffer pool instance建议刷新的数量slot->n_pages_requested作为该函数参数值,也就是依据建议刷新的页面数来进行刷新。
对于LRU列表的刷新的函数buf_flush_LRU_list将scan_depth 变量传递最终传递给buf_flush_LRU_list_batch 函数, 在通常情况下,可以简单的理解scan_depth的值来自于数据库参数innodb_lru_scan_deptch参数。 接下来看buf_flush_LRU_list_batch函数。
我们来看这个函数的循环体,我们来看退出循环的条件,满足任何一个条件退出:
1.如果free列表的长度大于innodb_lru_scan_depth,则中止循环。
2.被替换(evict_count)+被刷新(count)的页面数最多为scan_depth,scan_deptch可以简单理解为等于innodb_lru_scan_depth. 在看看循环里面的内容:
如果是一个可替换的页,则执行函数buf_LRU_free_page,将从LRU列表中摘除,其加入free列表。evict_count++
如果是脏页,则调用函数buf_flush_page_and_try_neighbors进行刷新,刷新数量累计到count值。
由此我们可以看出innodb_lru_scan_depth参数,在此起非常关键的作用,实际上也直接影响了buffer bool instance中的free列表的长度。
对于脏页列表批量刷新的函数。
slot->n_pages_requested:为之前介绍的刷新建议函数
page_cleaner_flush_pages_recommendation为该buffer pool instance所建议的需要刷新的页面的数量,实际刷新的页面并不一定等于该值。后面将详细介绍。这个值最终传递给buf_do_flush_list_batch函数的min_n参数。
我们来看一下这个函数,该函数主要逻辑也是一个for循环,我们来看一下循序中止的条件:
直到 count >= min_n 或者脏页列表为空。 即所刷新的page等于所建议的刷新数量, 或者“脏”页列表为空。
因为page cleaner线程调用该函数做批量刷新的时候,lsn_limit参数值为极大值,因此无需考虑page的oldest_modification。
刷新协调函数的执行一个刷新循环的最后一步,等待所有buffer pool instance刷新的完成。
函数特别简单,就是设置事件等待,等待所有buffer pool instance刷新完成的事件触发。
刷新完成之后,然后开始下一轮循环,如果刷新在1秒之内完成,则刷新协调线程会有短暂的sleep才会发起下一次刷新。期望是1秒钟进行一次所有buffer pool instance的批量刷新。