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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:
以下是边看代码边记录的,从磁盘读取一个压缩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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
关系型数据库 MySQL Shell
shell学习(十七) 【mysql脚本备份】
shell学习(十七) 【mysql脚本备份】
14 0
|
2月前
|
SQL 存储 关系型数据库
|
存储 关系型数据库 MySQL
|
2月前
|
SQL 缓存 关系型数据库
|
2月前
|
SQL 关系型数据库 MySQL
|
2月前
|
SQL 存储 关系型数据库
【MySQL 数据库】11、学习 MySQL 中的【锁】
【MySQL 数据库】11、学习 MySQL 中的【锁】
86 0
|
存储 关系型数据库 MySQL
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL 数据库】4、MySQL 事务学习
【MySQL 数据库】4、MySQL 事务学习
48 0
|
1月前
|
SQL 存储 关系型数据库
6本值得推荐的MySQL学习书籍
本文是关于MySQL学习书籍的推荐,作者在DotNetGuide技术社区和微信公众号收到读者请求后,精选了6本值得阅读的MySQL书籍,包括《SQL学习指南(第3版)》、《MySQL是怎样使用的:快速入门MySQL》、《MySQL是怎样运行的:从根儿上理解MySQL》、《深入浅出MySQL:数据库开发、优化与管理维护(第3版)》以及《高性能MySQL(第4版)》和《MySQL技术内幕InnoDB存储引擎(第2版)》。此外,还有12本免费书籍的赠送活动,涵盖《SQL学习指南》、《MySQL是怎样使用的》等,赠书活动有效期至2024年4月9日。
104 0
|
1月前
|
SQL 关系型数据库 MySQL
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)
轻松入门MySQL:深入学习数据库表管理,创建、修改、约束、建议与性能优化(3)