(续)为什么当磁盘IO成瓶颈之后数据库的性能急剧下降—性能更悲剧篇

  我们来梳理一下数据页访问的流程:   

   1. 先看缓存池中有没有,如果没有,则需要访问磁盘。

   2. 访问磁盘之前,是不是需要先提前准备好一个空闲的内存块来接收(存放)磁盘上的数据页的内容? 很显然,这个空闲内存块需要从缓存池中找。 现在抛出疑问: a .怎么找?   b. 如果内存池中目前没有空闲的内存块该怎么办? 是不是要先腾出空闲的内存块出来? 如果出现需要先腾出空闲的内存块出来的情况,那在访问这个数据页的时候(也就是上层读数据页的函数)将等待(执行)更长的时间,那性能可能就悲剧了。

      的确如此,当访问数据页的时候,缓存池没有命中, 这已经造成性能上的很大影响(如果配备高速磁盘,这个影响在一定程度上减小)。 但如果连在缓存池中找一个空闲的内存块来存放这个从磁盘读进来的数据页都没有, 那就更悲剧了。当出现这种情况时,那就得先腾出空闲的内存页出来。 下面将介绍这个部分的内容。 

        我们先来看函数buf_LRU_get_free_block的栈,该函数的作用是从缓存池中找到空闲的内存块。 另外, 在此次调式中,其上层函数有buf_read_page ——这个函数的作用是将磁盘上的数据页加载到buffer池中。

(图片看不清可以放大)


我们接下来看一下这个函数的注释:

函数注释翻译:

    1.  该函数被用户线程(也就是会话线程,非mysql的后台线程)调用,作用是从缓存池中获取一个空闲的内存块供被读入的数据页使用,被使用的空闲内存块将从空闲列表中移走(摘除)

    2. 如果缓存池中目前没有空余的内存块,也就是空余列表中不存在内存块,则需要从LRU列表中取出内存块放到空闲列表中, 然后重新从空闲列表中取 。 循环规则如下:

         第一次循环(也就是iteration=0):

              从空闲列表中找,找到返回,没找到,进入下面的内容。

              step 1     如果buf_pool->try_LRU_scan为真,(其他的用户线程扫描过LRU列表时,发现已经没有可以替换的数据块了, 然后设置该变量为false,目的是告诉其他用户线程先不要扫描LRU列表,“别浪费功夫”)  ,则进行LRU列表扫描,调用的函数为 buf_LRU_scan_and_free_block, 扫描深度为srv_LRU_scan_depth,这个是参数变量中lru_scan_depth中设置。但该注释实际上跟5.7.17的代码不匹配,其他版本未查,实际的扫描深度是BUF_LRU_SEARCH_SCAN_THRESHOLD,也就是100个内存 块(page)。 如果找到一个可以替换的内存块(page),则将该内存块(page)放入free   列表,然后跳转到loop代码段, 重新从free列表里面找空余块。 但需要注意的是,因为free列表供所有的用户会话线程使用,因此不保证该线程产生的free页将 一定会被其使用,有可能被其他用户线程抢占使用。                                 

            如果step 1 没有产生free的页,则进入step 2 .

             step 2   如果在LRU列表里面,没有找到可以替换的页,则从脏页列表里面找,执行函数buf_flush_single_page_from_LRU, 但是因为是脏页,必须刷盘之后, 才能被加入free列表, 此时将涉及到同步的刷盘物理IO,将给用户会话执行sql的速度带来严重的影响。 如果需要执行这段代码来产生空余页,数据库的性能必将非常低下。

          第二次循环(也就是iteration=1):

                             跟第一次循环相同, 仅仅有以下区别。

                             a.  将对整个LRU列表扫描,来查找可替换的内存块,而不是仅仅扫描100个数据页。 

                             b.  即使buf_pool->try_LRU_scan 为false, 也进行整个LRU列表扫描。

         第N次循环(也就是iteration>1):                               

                            跟上面的循环一致,增加每循环一次sleep 10毫秒。 

       下面这个函数的循环体的核心内容(为了截图方便,作者剔除了非核心代码),看不懂代码的看代码内的注释就可以了。(续)为什么当磁盘IO成瓶颈之后数据库的性能急剧下降—性能更悲剧篇

          至此,大家应该了解:

 1. 访问数据页时,在缓存池中没有命中,将严重影响性能。 

2. 当没有命中数据页时,  且当内存池中没有空余的block来缓存新的数据页时,需要从LRU列表中找可以替换页来缓存数据页时,将会给性能带来更严重的影响。

   3. 当在LUR列表中找不到可以替换的页时,需要刷新脏页来缓存新的数据页时,性能将变得非常严重。 

       因此,提高缓存的命中率,以及让缓存中有更多的空闲页可以供使用,将会避免上面的情况。 但是,缓存分两部分,一个是LRU列表中的缓存数据的block,一个是FREE列表中的block.  则需要在两者之间做权衡。当free多,则缓存数据页的块就少了。  反之,亦然。 

      说了这么多,跟文章标题有什么关系呢? 实际上这样的,当磁盘IO存在瓶颈的时候,MYSQL后台的刷新数据页的线程不能及时地将脏页刷到磁盘,最后变成空闲页,导致空闲页供不应求,从而出现上述情况。对于mysql后台的数据页刷新线程的工作机制,之前有篇文件简单的介绍了一部分内容,更全面的解析且看后续的文章。


上一篇:leetcode题目-LRU缓存机制


下一篇:LRU问题 Go版本