mysql的缓存池中的LRU列表实现机制源码解析

  曾道听途说,mysql的缓存池,"一个是young的,一个是old ,young池缓存的是最近使用的页,old池是存放老旧的页,也就是一段时间没有使用的页,假如存在全表扫描的时候,大量的数据页要占据缓存池,但不会把热点页( 也就是young的缓存页)换出去,这样以来,的确是个完美的解决方案”。但未曾知道听说的是否真正的正确,如果正确,mysql到底是如何实现呢 ?现在告诉大家答案。

       学习过mysql的人都知道,mysql存在一个LRU列表,即"近期最少使用"列表,用来缓存最热的数据,既然是列表,那是怎么来实现排队的呢?怎样才能实现上述的功能-----做大表全表扫描的时候,不把最热的数据挤出去。

       为了便于简单的了解lru列表的机制,追根溯源,从下面2个方面考虑:

   a. 一个数据页从磁盘被读入缓存池,它该怎么加入lru列表, 加在什么位置?

   b. 如果存在之前道听途说的“新(young)池,老(old)池”,一个页在什么情况下,从老池中移动到新池?反之也是一个疑问。


    翻了翻mysql的源码,找到这个函数buf_LRU_add_block_low,从名字大概就能猜出其作用,将block加入到buffer的LRU列表。

   我们来看一下函数的栈,原来它的上层函数为buf_read_page(该函数的作用就是读取数据页),所以我们知道,page加入LRU列表的时候,是读入并加载到缓存池的时候, 同时,buf_LRU_add_block_low 函数的old变量传入的值为1,即需要加入到“old池”中。

mysql的缓存池中的LRU列表实现机制源码解析


我们看一下buf_LRU_add_block_low 这个函数的内容,为了显示方便,作者剔除了非主要逻辑的内容,不影响函数的完整功能的实现。

我们来解析这个函数:

    a. 入参old是 bool类型,用来表示该page是否应该加入 old “池”中,函数注释也有详细的说明。当为true时,加入到old池中。 

    b. (!old || (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN)   如果old为false或者缓存池中的LRU列表的长度小于BUF_LRU_OLD_MIN_LEN ,则执行UT_LIST_ADD_FIRST(buf_pool->LRU, bpage);  也就是加入到LRU列表的头部。  但当page加载到缓存池时,old为true,因此,能否加入到LRU列表的头部得看目前LRU列表的长度是否小于BUF_LRU_OLD_MIN_LEN, 该值为宏,定义为512. 

    c. 当不满足b说明的条件时,则执行下面的动作,也就是else结构体里面的内容。

    UT_LIST_INSERT_AFTER(buf_pool->LRU, buf_pool->LRU_old,

bpage);

buf_pool->LRU_old_len++;

   

   UT_LIST_INSERT_AFTER 的作用就是将bpage 页插入到LRU列表中的LRU_old页的后面。 原来,所谓的“老池 ”跟“年轻池 ”的区别,仅仅依靠LRU_old 这个页来区分,在LRU列表中,LRU_old 之前的页是所谓的“年轻池”中的数据页,LRU_old 以及之后的页为“老池”中的页。数据页从磁盘加载到buffer时,先加入“老池”,而不是加在LRU列表的头部。 然后“old池”中的page数量加1.


     d. 因为LRU列表总长度变成了,所以缓存的数据页的总大小也变了,所以 有 incr_LRU_size_in_bytes(bpage, buf_pool)。

     e. 再接下来,如果满足UT_LIST_GET_LEN(buf_pool->LRU) > BUF_LRU_OLD_MIN_LEN 条件,(见上面的说明),将该数据页打上old标签。buf_page_set_old(bpage, old); 同时,调整“旧池”的长度buf_LRU_old_adjust_len(buf_pool); 这个函数不再展开说明。

    f. 后面的情况省略讲解。    


上面的函数,就是数据页加载到buffer时,加入LRU列表的动作,至此,各位应该非常清楚数据页以old或者young标签加入到LRU列表中所触发的不同动作。

以young标签加入(即old=0),则加入到lru列表的头部。

以old标签加入(即old=1),则加入到buf_pool->LRU_old页的后面。这也是数据页从磁盘加载到buffer时会执行的动作。这样可以避免刚刚进入缓冲池中的页换走LRU列表头部的页。


    接下来的问题, 被缓存的数据页如何从“旧池”换到“新池” ? 继续翻代码,翻到这个函数。buf_LRU_make_block_young


mysql的缓存池中的LRU列表实现机制源码解析

 函数特别简单,最体现函数逻辑的就下面2行。

     buf_LRU_remove_block(bpage), 将缓存数据页从LRU列表中移除。呀,不要了? 别担心,看下面。

    buf_LRU_add_block_low(bpage,FALSE), 这个函数我们上面已经介绍,就是将page加入LRU列表,但请注意, 这次给old参数传入的是FALSE。即这个缓存页将插入LRU的头部。 


    知道这个函数是将数据页由old变成yong,但触发条件是什么呢? 只能定位这个函数的上层函数了。 我们来看这个函数的栈。

mysql的缓存池中的LRU列表实现机制源码解析

其上层函数有个名称叫 buf_page_make_young_if_needed, 从名称来看,

就是决定是否将page变成young的函数。再次提一下,变成young就是插入到LRU列表的头部。

    该函数buf_page_make_young_if_needed特别简单,就两行。

if (buf_page_peek_if_too_old(bpage)) {

buf_page_make_young(bpage);

}

根据buf_page_peek_if_too_old 函数是否返回true来决定是否变成young。

下面是buf_page_peek_if_too_old  函数的内容

mysql的缓存池中的LRU列表实现机制源码解析


能返回true的条件是:

if (access_time > 0

    && ((ib_uint32_t) (ut_time_ms() - access_time))

    >= buf_LRU_old_threshold_ms)

其中,access_time 是page第一次被访问的时间,buf_LRU_old_threshold_ms 由mysql的参数变量innodb_old_blocks_time 决定,默认为1000ms, 也就是1秒。 ut_time_ms() - access_time >=buf_LRU_old_threshold_ms也就是这次访问的时间跟第一次访问的时间间隔相差1s以上,满足这个条件,则该页将会被放入LRU列表的头部。

       以上就是mysql中LRU的机制中最核心的部分,亲是否在这个知识点上也曾跟作者一样迷惑?




上一篇:vim常用命令


下一篇:Candence 汉字显示重叠