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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介:
当我们将一个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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
缓存 关系型数据库 MySQL
MySQL并发支撑底层Buffer Pool机制详解
【10月更文挑战第18天】在数据库系统中,磁盘IO操作是性能瓶颈之一。为了提高数据访问速度,减少磁盘IO,MySQL引入了缓存机制。其中,Buffer Pool是InnoDB存储引擎中用于缓存磁盘上的数据页和索引页的内存区域。通过缓存频繁访问的数据和索引,Buffer Pool能够显著提高数据库的读写性能。
79 2
|
17天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,并与使用 RPM 包安装进行了对比
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,并与使用 RPM 包安装进行了对比。通过具体案例,读者可以了解如何准备环境、下载源码、编译安装、配置服务及登录 MySQL。编译源码安装虽然复杂,但提供了更高的定制性和灵活性,适用于需要高度定制的场景。
50 3
|
20天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据需求选择最合适的方法。通过具体案例,展示了编译源码安装的灵活性和定制性。
61 2
|
19天前
|
存储 SQL NoSQL
|
1月前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置服务等,并与使用 RPM 包安装进行了对比,帮助读者根据需求选择合适的方法。编译源码安装虽然复杂,但提供了更高的定制性和灵活性。
226 2
|
1月前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤
【10月更文挑战第7天】本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据自身需求选择合适的方法。
58 3
|
1月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
45 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
2月前
|
SQL 缓存 关系型数据库
揭秘MySQL一条SQL语句的执行流程
以上步骤共同构成了MySQL处理SQL语句的完整流程,理解这一流程有助于更有效地使用MySQL数据库,优化查询性能,及时解决可能出现的性能瓶颈问题。
92 7
|
2月前
|
存储 缓存 关系型数据库
深度解密 MySQL 的 Buffer Pool
深度解密 MySQL 的 Buffer Pool
37 0
|
3月前
|
存储 自然语言处理 关系型数据库
MySQL全文索引源码剖析之Insert语句执行过程
【8月更文挑战第17天】在MySQL中,处理含全文索引的`INSERT`语句涉及多步骤。首先进行语法解析确认语句结构无误;接着语义分析检查数据是否符合表结构及约束。随后存储引擎执行插入操作,若涉及全文索引则进行分词处理,并更新倒排索引结构。此外,事务管理确保了操作的完整性和一致性。通过示例创建含全文索引的表并插入数据,可见MySQL如何高效地处理此类操作,有助于优化数据库性能和提升全文搜索效果。