[MySQL 源码] 从buffer pool中获取空闲block流程

当我们将一个page读入内存时,需要先为其分配一个block,从buffer pool中获取。入口函数为buf_LRU_get_free_block

之前在http://mysqllover.com/?p=303有简要介绍,这里详细看看,当然,跟最近博客的主题一样,我们还是主要针对压缩表来分析。

以下分析基于Percona Server 5.5.18

buf_LRU_get_free_block

loop:

1.block = buf_LRU_get_free_only(buf_pool)

首先从buf_pool->free链表尾部读取,如果有空闲页,则将其从buf_pool->free中移除,设置bpage->state=BUF_BLOCK_READY_FOR_USE,然后返回

上述流程需要加buf_pool->free_list_mutex锁

2.如果1获得了一个block,还需要重置压缩页描述符block->page.zip为0,然后直接返回

3.如果在buf_pool->free上没有block,则从buf_pool->LRU或unzip_LRU的尾部开始扫描,尝试找一个空闲block.

freed = buf_LRU_search_and_free_block(buf_pool, n_iterations); //n_iterations是一个计数器,表示尝试释放但失败的次数。

A.持有buf_pool->LRU_list_mutex锁

B.首先,尝试从buf_pool->unzip_LRU上释放block,这种情况下不会释放压缩页数据

freed = buf_LRU_free_from_unzip_LRU_list(buf_pool, n_iterations, have_LRU_mutex);

>>判断是否从unzip_LRU上驱逐block

理论上讲,从buf_pool->unzip_LRU上应该更容易获得一个block,因为我们可以选择一个脏块(只驱逐解压页),但当我们尝试5次还是没有找到时,则直接返回到正常的驱逐block的逻辑,即从LRU上获取

另外也有函数buf_LRU_evict_from_unzip_LRU用来判断是否从unzip_LRU上驱逐block,其判断逻辑如下:

>>>需要持有buf_pool->LRU_list_mutex

>>>如果buf_pool->unzip_LRU长度为0 ,返回FALSE

>>>如果buf_pool->unzip_LRU小于buf_pool->LRU的十分之一,返回FALSE

>>>如果buf_pool->freed_page_clock == 0,表示之前没有进行过任何block驱逐,默认假设工作负载为disk bound,返回TRUE

freed_page_clock是一个序列号,用来计数从LRU尾部移除的block数,可以不加锁读取该变量

>>>计算最近的平均IO量

    io_avg = buf_LRU_stat_sum.io / BUF_LRU_STAT_N_INTERVAL

        + buf_LRU_stat_cur.io;

最近50秒的平均IO+当前的IO,获得最近的平均IO负载

    unzip_avg = buf_LRU_stat_sum.unzip / BUF_LRU_STAT_N_INTERVAL

        + buf_LRU_stat_cur.unzip;

最近50秒平均解压page的次数+当前解压page的次数,获得最近每秒平均解压page次数

我们对io_avg进行加权(BUF_LRU_IO_TO_UNZIP_FACTOR),依次判断是IO-BOUND还是CPU-BOUND

当unzip_avg <= io_avg * BUF_LRU_IO_TO_UNZIP_FACTOR时,表示这是IO-Bound的,返回TRUE

当unzip_avg  >  io_avg * BUF_LRU_IO_TO_UNZIP_FACTOR时,表示这是CPU-Bound的,返回FALSE。

BUF_LRU_IO_TO_UNZIP_FACTOR是一个宏,值为50。这是一个hard code值,但正如bug#64181所提到的,快速存储例如SSD可能并不适合这样的加权,因为SSD相对传统硬盘具有更快的IO速度。

当从buf_LRU_evict_from_unzip_LRU返回值为false时,则从LRU扫描,如果为true,则尝试从unzip_lru扫描

>>计算在unzip中的最大扫描距离

    distance = 100 + (n_iterations

              * UT_LIST_GET_LEN(buf_pool->unzip_LRU)) / 5

                    其中n_iterations<5

>>从buf_pool->unzip_LRU尾部开始扫描,满足如下条件可以进行驱逐

buf_block_get_state(block) == BUF_BLOCK_FILE_PAGE;

block->in_unzip_LRU_list;

block->page.in_LRU_list;

然后从unzip_lru上释放block:

freed = buf_LRU_free_block(&block->page, FALSE, have_LRU_mutex);

>>>检查block是否被pin住(buffer-fixed or I/O-fixed),是的话直接返回false

>>>如果表正在被删除(bpage->space_was_being_deleted) 并且bpage->oldest_modification !=0时调用buf_flush_remove(bpage)从flush list上删除该block

其中bpage->oldest_modification记录了修改该block,但尚未刷入磁盘的日志记录起始LSN。值为0表示所有的修改都刷入了磁盘。

>>>分配一个page描述符(buf_page_t)

b = buf_page_alloc_descriptor();

然后将bpage拷贝到b中

 memcpy(b, bpage, sizeof *b);

这里bpage->zip.data的指针也被拷贝到b,因此可以通过b访问压缩页,而在随后将bpage->zip.data置为NULL

>>>从LRU和Page hash上移除page

buf_LRU_block_remove_hashed_page(bpage, zip)

当前流程的zip为false,表示不释放压缩page

|–>buf_LRU_remove_block(bpage);

|–>从buf_pool->page_hash中移除

这里zip参数为false,因此无需释放压缩页数据。但如果从LRU释放,这里可能会成为瓶颈(buf_buddy_free做碎片整理)

>>>将b插入到buf_pool->page_hash和buf_pool->LRU中。

>>>移除可能存在的adaptive hash index记录

btr_search_drop_page_hash_index((buf_block_t*) bpage, NULL);

>>>计算b->zip.data的checksum,并写入压缩页。

>>>将空出来的bpage加入到buf_pool->free上

     buf_LRU_block_free_hashed_page((buf_block_t*) bpage, FALSE);

C.如果buf_pool->unzip_LRU上找不到空闲块,这时候会去从buf_pool->LRU上获取,这种情况下,如果驱逐的是压缩表的block,还会释放压缩页

    if (!freed) {

        freed = buf_LRU_free_from_common_LRU_list(

            buf_pool, n_iterations, have_LRU_mutex);

    } 

该函数会从buf_pool->LRU的尾部开始扫描,在没有压缩表的情况下这也是普遍调用的函数

函数freed = buf_LRU_free_block(bpage, TRUE, have_LRU_mutex)会被调用到去从LRU上释放该块,注意这里第二个参数为TRUE,这表明会同时释放压缩页,并做碎片整理工作。

buf_LRU_free_block->buf_LRU_block_remove_hashed_page->buf_buddy_free->buf_buddy_free_low,另外

buf_LRU_block_remove_hashed_page中还会释放bpage的page描述符(buf_page_free_descriptor)

related bug:http://bugs.mysql.com/bug.php?id=64344

如bug#64344所提到的,malloc/free是在持有buffer pool锁的情况下进行的,这会对并发操作产生开销。

D.更新buf_pool->LRU_flush_ended计数并释放buf_pool->LRU_list_mutex

    if (!freed) {

        buf_pool->LRU_flush_ended = 0;

    } else if (buf_pool->LRU_flush_ended > 0) {

        buf_pool->LRU_flush_ended–;

    }

4.从步骤3成功释放了一个空闲block,则goto loop

5.n_iterations > 30时,开始打印警告和监控信息到err log中

6.如果走到这一步,表明找不到空闲块,尝试刷LRU, 并唤醒AIO线程

buf_flush_free_margin(buf_pool, TRUE);

7.如果buf_pool->LRU_flush_ended > 0,表明我们已经在一次LRU Flush中写入了page,为了让insert 

buffer更高效,将这些page转移到free list上(翻译自注释)

buf_LRU_try_free_flushed_blocks(buf_pool);

8.n_iterations > 10时os_thread_sleep(500000)

9.n_iterations++; goto loop

时间: 2024-12-28 10:34:06

[MySQL 源码] 从buffer pool中获取空闲block流程的相关文章

MySQL · 源码分析 · InnoDB LRU List刷脏改进之路

之前的一篇内核月报MySQL · 引擎特性 · InnoDB Buffer Pool 中对InnoDB Buffer pool的整体进行了详细的介绍.文章已经提到了LRU List以及刷脏的工作原理.本篇文章着重从MySQL 5.7源码层面对LRU List刷脏的工作原理,以及Percona针对MySQL LRU Flush的一些性能问题所做的改进,进行一下分析. 在MySQL中,如果当前数据库需要操作的数据集比Buffer pool中的空闲页面大的话,当前Buffer pool中的数据页就必须

Linux CentOS6.6系统中安装mysql源码包的方法_Linux

这里以CentOS6.6系统中安装MySQL的源码包,进行讲解. 1. mysql源码包的下载 mysql安装包的官方下载地址为:http://dev.mysql.com/downloads/mysql/5.6.html 打开该下载地址后,在 "Select Version:"处,选择要下载的mysql的版本,我选择的是5.6.34:在"Select Platform:"处,选择适用的操作系统类型,由于是下载源码包,故这里我们要选择Source Code. 之后,会

在eclipse中配置MySQL源码环境(r12笔记第14天)

今天费了些周折,总算搭建好了MySQL源码的调试环境,主要的目的就是想在看代码的时候有一些头绪,让这些开发技巧派上用场.不至于盲人摸象一般的拿着命令肉眼扫视,当然对于代码至于能不能啃下来,那是另外一回事了. 我来说说我的情况,Java开发还有一点基础,所以以前的eclipse还算用得比较熟悉.大家知道InnoDB的源码是c,MySQL Server的是c++,这样一套环境想调试好,如果没有这方面的平台开发经验其实还是有一点难度的.最后我还是决定使用eclipse来做,基于Windows平台. 里

MySQL · 引擎特性 · InnoDB Buffer Pool

前言 用户对数据库的最基本要求就是能高效的读取和存储数据,但是读写数据都涉及到与低速的设备交互,为了弥补两者之间的速度差异,所有数据库都有缓存池,用来管理相应的数据页,提高数据库的效率,当然也因为引入了这一中间层,数据库对内存的管理变得相对比较复杂.本文主要分析MySQL Buffer Pool的相关技术以及实现原理,源码基于阿里云RDS MySQL 5.6分支,其中部分特性已经开源到AliSQL.Buffer Pool相关的源代码在buf目录下,主要包括LRU List,Flu List,Do

Linux下MySQL源码编译安装(eg:mysql-5.6.27.tar.gz )

Linux下MySQL源码安装(eg:mysql-5.6.27.tar.gz ): 1:准备MySQL源码安装包: mysql-5.6.27.tar.gz.cmake-3.3.2.tar.gz.ncurses-6.0.tar.gz 注:centos请安装: yum install -y ncurses-devel yum install -y perl-Module-Install.noarch 网址: https://cmake.org/download/ ftp://invisible-is

[MySQL 源码] MySQL drop table(压缩表)效率与流程分析

 之前发生过一起连续drop压缩表,最后长时间等待信号量crash,线上alert log里的报错是: OS WAIT ARRAY INFO: reservation count 36647199, signal count 34050225 --Thread 1331538240 has waited at row0purge.c line 680 for 950.00 seconds the semaphore: S-lock on RW-latch at 0xe60b60 '&dict_o

MySQL 5.7 对buffer pool list scan的优化

worklog: http://dev.mysql.com/worklog/task/?id=7047 原作者Innam 跳槽到twitter后 将该特性合并到webscalesql: https://github.com/webscalesql/webscalesql-5.6/commit/c834781321d97b914c3712c663bd7209175a3fe2#diff-07468177c19cc3f32d25913fe731f936R1814 在该worklog中,主要对LRU L

MySQL · 特性分析 · innodb buffer pool相关特性

背景 innodb buffer pool做为innodb最重要的缓存,其缓存命中率的高低会直接影响数据库的性能.因此在数据库发生变更,比如重启.主备切换实例迁移等等,innodb buffer poll 需要一段时间预热,期间数据库的性能会受到明显影响. 另外mysql 5.7以前innodb buffer pool缓存大小修改不是动态的,重启才能生效.因此innodb buffer pool的预热和innodb buffer pool大小的动态修改,对性能要求较高的应用来说是不错的特性,下面

Linux下mysql源码安装笔记_Mysql

1.假设已经有mysql-5.5.10.tar.gz以及cmake-2.8.4.tar.gz两个源文件 (1)先安装cmake(mysql5.5以后是通过cmake来编译的) [root@ rhel5 local]#tar -zxv -f cmake-2.8.4.tar.gz [root@ rhel5 local]#cd cmake-2.8.4 [root@ rhel5 cmake-2.8.4]#./configure [root@ rhel5 cmake-2.8.4]#make [root@