[MySQL学习] 一个压缩Page从磁盘读入buffer pool的过程

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
简介:
以下是边看代码边记录的,从磁盘读取一个压缩Page到buffer pool的的全过程,以函数buf_page_get_gen作为入口
buf_page_get_gen

1.根据space和offset来计算请求的page是否已经读到了buffer pool中

fold = buf_page_address_fold(space, offset);
block = (buf_block_t*) buf_page_hash_get_low(
                          buf_pool, space, offset, fold);

判断:如果这个block所对应表正在被drop掉(使用lazy drop table),则调用buf_LRU_free_block((buf_page_t*)block, TRUE, TRUE)将其从LRU中删除
block->page.space_was_being_deleted在函数buf_LRU_mark_space_was_deleted内被设置

2.if (block && buf_pool_watch_is_sentinel(buf_pool, &block->page))   
则将block设置为NULL。

buffer pool的watch[BUF_POOL_WATCH_SIZE];成员暂不清楚其用途,似乎用在insert buffer上,回头专门研究下
是在MySQL5.5.5引入
一个普通读的mode值为BUF_GET,因此以下也会忽略掉不必要的代码流程

3.当page不在bp时,则从磁盘读取该page。

        if (buf_read_page(space, zip_size, offset, trx)) {
             buf_read_ahead_random(space, zip_size, offset,
                          ibuf_inside(mtr), trx);
另外还会顺便做一下预读

buf_read_page是读取page的接口函数,其调用栈如下:

a.获取 tablespace_version = fil_space_get_version(space),用于随后判断该space是否正在被删除或者tablespace被DISCARD/IMPORT
 
b.调用如下函数:
407     count = buf_read_page_low(&err, TRUE, BUF_READ_ANY_PAGE, space,
408                   zip_size, FALSE,
409                   tablespace_version, offset, trx);

buf_read_page_low
–>bpage = buf_page_init_for_read(err, mode, space, zip_size, unzip,
                        tablespace_version, offset);
      |–>计算page hash中的对应hash值fold = buf_page_address_fold(space, offset);
      |–>加buf_pool->LRU_list_mutex和buf_pool->page_hash_latch的x锁
      |–>从page hash中读取对应Page,如果已经在bp中,并且该tablespace正在被删除,则删掉该page( buf_LRU_free_block(watch_page, TRUE, TRUE);)
      |–>fil_tablespace_deleted_or_being_deleted_in_mem检查tablespace是否正在被删除
      |–>data = buf_buddy_alloc(buf_pool, zip_size, &lru, TRUE); 为压缩Page分配内存
      |–>分配Page描述符(buf_page_t)及初始化其字段信息
      |–>将其插入到page hash中。
          HASH_INSERT(buf_page_t, hash, buf_pool->page_hash, fold,
                  bpage);
      |–>插入到LRU LIST的old段buf_LRU_add_block(bpage, TRUE/* to old blocks */);
      |–>buf_page_set_io_fix(bpage, BUF_IO_READ);设置bpage->io_fix为BUF_IO_READ,表示将从磁盘读取Page

–> 如果bpage为NULL,则返回;这里触发的bug#43948, Perocna已经移植patch,官方还没修复

–>调用*err = _fil_io(OS_FILE_READ | wake_later,
                  sync, space, zip_size, offset, 0, zip_size,
                  bpage->zip.data, bpage, trx);
      |–>fil_mutex_enter_and_prepare_for_io(space_id);
            |–>mutex_enter(&fil_system->mutex); 加fil_system的互斥锁
            |–>如果是系统表空间或者日志文件,直接返回,这些文件总是一直打开的
            |–>如果fil_system->n_open < fil_system->max_n_open,即当前打开的innodb文件数小于最大允许数目(由参数innodb_open_files控制),直接返回
            |–>如果当前表空间正在被rename(space->stop_ios为true),则retry,直到rename完成
            |–>如果文件已经打开,返回
            |–>尝试关闭一些文件(fil_try_to_close_file_in_LRU),以保证打开的文件数少于innodb_open_files
            |–>依旧大于innodb_open_files,则唤醒IO线程(os_aio_simulated_wake_handler_threads,使用srv_use_native_aio不许要通知)
            |–>fil_flush_file_spaces刷新表空间,然后再retry

     |–>fil_node_prepare_for_io  //如果文件没打开,则将其打开(fil_node_open_file),并且node->n_pending++
     |–>mutex_exit(&fil_system->mutex);
     |–>根据zip_size计算读文件的偏移量(offset_high,offset_low)
     |–>异步读文件
        ret = os_aio(type, mode | wake_later, node->name, node->handle, buf,
                  offset_low, offset_high, len, node, message, space_id, trx);

     |–>当mode == OS_AIO_SYNC时,fil_node_complete_io(node, fil_system, type);更新fil_node_t

–>sync为true,则buf_page_io_complete(bpage);
     |–>获取bpage->zip.data中记录的space id(偏移量FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID)和page no(偏移量FIL_PAGE_OFFSET),跟bpage中对应字段进行对比
     |–>函数buf_page_is_corrupted用于检测page是否损坏
           |–>对于非压缩Page,检查存储在Page头和尾部的日志序列号(LSN)是否相同
           |–>检查Page中的LSN是否大于当前的LSN
           |–>当innodb_checksums打开时,计算checksum并和记录在page中的checksum(偏移量FIL_PAGE_SPACE_OR_CHKSUM)做比较
                |–>对于压缩表,调用page_zip_calc_checksum来计算checksum,
                |–>对于非压缩表:
                a.老版本的innodb:buf_calc_page_old_checksum
                b.当前默认:buf_calc_page_new_checksum
                c.开启innodb_fast_checksum:buf_calc_page_new_checksum_32
     |–>buf_page_set_io_fix(bpage, BUF_IO_NONE);

c.如果有必要的话,刷buffer pool的LRU LIST,调用buf_flush_free_margin(buf_pool, TRUE);
buf_flush_free_margin 用于刷新LRU LIST的脏页,调用栈如下:
|–>n_to_flush = buf_flush_LRU_recommendation(buf_pool)//计算flush的page数以保证有足够可替换的block
|–>n_flushed = buf_flush_LRU(buf_pool, n_to_flush)
|–>buf_flush_wait_batch_end
这部分代码后面单独再分析
    
4.调用预读函数buf_read_ahead_random,如果设置了innodb_random_read_ahead会进行预读

5.goto loop && block = (buf_block_t*) buf_page_hash_get_low(buf_pool, space, offset, fold);

6.switch (buf_block_get_state(block))
case BUF_BLOCK_ZIP_PAGE:
case BUF_BLOCK_ZIP_DIRTY:

—>如果当前bpage是buffer-fixed or I/O-fixed,则sleep一段时间,goto loop

—>block = buf_LRU_get_free_block(buf_pool);从buffer pool的free list(或者lru尾)获取一个空闲block
   a.free+lru的长度<buf_pool->curr_size / 20,直接断言错误,lock heaps或者adpative hash index占用太多的buffer pool
    (关于Innodb内存分配管理,后续分析的点)

   b.free+lru<buf_pool->curr_size / 3,打印warning

   c.block = buf_LRU_get_free_only(buf_pool); 从free list上找一个空闲block

   d.如果free list上没有空闲的,则调用buf_LRU_search_and_free_block从LRU上获取
       |–>首先尝试unzip__lru上释放一个Uncompressed page,调用函数buf_LRU_free_from_unzip_LRU_list
           |–>判断是否可以从unzip_lru上释放block(buf_LRU_evict_from_unzip_LRU)
           |–>从unzip_lru上释放一个block,调用freed = buf_LRU_free_block(&block->page, FALSE, have_LRU_mutex);第二个参数为false表示不释放压缩Page.
       |–>如果没有的话,再调用buf_LRU_free_from_common_LRU_list从LRU上释放一个clean page

    e.找不到空闲blcok,尝试刷LRU LIST buf_flush_free_margin(buf_pool, TRUE) 并将其移动到free list上(buf_LRU_try_free_flushed_blocks)

—>检查
a.刚刚读入的page是否被修改了,如果被修改了,则释放(buf_LRU_block_free_non_file_page)并重新loop2. 

b.该page是否是buffer-fix 或 io-fix,是的话,则goto wait_until_unfixed

—>将compressed page  memcpy到刚刚分配的block中,调用函数buf_relocate(bpage, &block->page);
a.memcpy(dpage, bpage, sizeof *dpage);
b.将bpage从LRU移除,并重新插入dpage
c.将bpage从Page hash中移除,并重新插入dpage

—>插入到unzip_lru头部:
buf_unzip_LRU_add_block(block, FALSE);
buf_block_set_io_fix(block, BUF_IO_READ);

—>释放bpage(已经memcpy到block->page中),buf_page_free_descriptor(bpage);

—>解压文件
success = buf_zip_decompress(block, FALSE);   //这里我们把第二个参数调整为FALSE,表示在解压时不做checksum验证

—>调用ibuf_merge_or_delete_for_page执行该page上所有缓存的操作,并删除对应的insert buffer记录

然后设置io_fix:buf_block_set_io_fix(block, BUF_IO_NONE);

7.buf_page_set_accessed_make_young

8.第一次读取page(access_time=0),执行一次buf_read_ahead_linear

遗留的问题:
1.innodb如何进行预读,可以看到有两次预读,需要了解下(一次random read ahead–buf_read_ahead_random, linear read ahead–buf_read_ahead_linear)

2.buf_pool->watch数组的作用不甚明了

3.从磁盘读取Page后顺带刷脏页的过程buf_flush_free_margin 

4.从磁盘读取page后,对insert buffer的操作ibuf_merge_or_delete_for_page

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
5月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
674 6
|
6月前
|
关系型数据库 MySQL 数据管理
Mysql基础学习day03-作业
本内容包含数据库建表语句及多表查询示例,涵盖内连接、外连接、子查询及聚合统计,适用于员工与部门数据管理场景。
110 1
|
6月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day01
本课程为MySQL基础学习第一天内容,涵盖MySQL概述、安装、SQL简介及其分类(DDL、DML、DQL、DCL)、数据库操作(查询、创建、使用、删除)及表操作(创建、约束、数据类型)。适合初学者入门学习数据库基本概念和操作方法。
229 6
|
6月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day02-作业
本教程介绍了数据库表的创建与管理操作,包括创建员工表、插入测试数据、删除记录、更新数据以及多种查询操作,涵盖了SQL语句的基本使用方法,适合初学者学习数据库操作基础。
137 0
|
6月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day03
本课程为MySQL基础学习第三天内容,主要讲解多表关系与多表查询。内容涵盖物理外键与逻辑外键的区别、一对多、一对一及多对多关系的实现方式,以及内连接、外连接、子查询等多表查询方法,并通过具体案例演示SQL语句的编写与应用。
174 0
|
6月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day01-作业
本教程包含三个数据库表的创建练习:学生表(student)要求具备主键、自增长、非空、默认值及唯一约束;课程表(course)定义主键、非空唯一字段及数值精度限制;员工表(employee)包含自增主键、非空字段、默认值、唯一电话号及日期时间类型字段。每个表的结构设计均附有详细SQL代码示例。
116 0
|
6月前
|
SQL 关系型数据库 MySQL
Mysql基础学习day02
本课程为MySQL基础学习第二天内容,涵盖数据定义语言(DDL)的表查询、修改与删除操作,以及数据操作语言(DML)的增删改查功能。通过具体SQL语句与实例演示,帮助学习者掌握MySQL表结构操作及数据管理技巧。
189 0
|
缓存 关系型数据库 MySQL
MySQL并发支撑底层Buffer Pool机制详解
【10月更文挑战第18天】在数据库系统中,磁盘IO操作是性能瓶颈之一。为了提高数据访问速度,减少磁盘IO,MySQL引入了缓存机制。其中,Buffer Pool是InnoDB存储引擎中用于缓存磁盘上的数据页和索引页的内存区域。通过缓存频繁访问的数据和索引,Buffer Pool能够显著提高数据库的读写性能。
642 2
|
SQL 存储 关系型数据库
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
本文详细介绍了MySQL中的SQL语法,包括数据定义(DDL)、数据操作(DML)、数据查询(DQL)和数据控制(DCL)四个主要部分。内容涵盖了创建、修改和删除数据库、表以及表字段的操作,以及通过图形化工具DataGrip进行数据库管理和查询。此外,还讲解了数据的增、删、改、查操作,以及查询语句的条件、聚合函数、分组、排序和分页等知识点。
1227 56
【MySQL基础篇】全面学习总结SQL语法、DataGrip安装教程
|
12月前
|
存储 SQL 关系型数据库
mysql的undo log、redo log、bin log、buffer pool
MySQL的undo log、redo log、bin log和buffer pool是确保数据库高效、安全和可靠运行的关键组件。理解这些组件的工作原理和作用,对于优化数据库性能和保障数据安全具有重要意义。通过适当的配置和优化,可以显著提升MySQL的运行效率和数据可靠性。
283 16

推荐镜像

更多