技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> MySQL --> MySQL数据库InnoDB存储引擎 Buffer Pool页面分配详解

MySQL数据库InnoDB存储引擎 Buffer Pool页面分配详解

浏览:1339次  出处信息
MySQL数据库InnoDB存储引擎  Buffer Pool页面分配(innodb_buffer_pool_size)

当用户需要读取一个在外存中的页面,或者是分配一个新的页面进行插入,就需要调用buf0flu.cc::buf_LRU_get_free_block函数进行页面的分配。一个完整的MySQL数据库InnoDB存储引擎页面分配流程需要包含以下几个步骤:

1.尝试从Buffer Pool的free list中分配新页面,大部分情况下,Buffer Pool的free list都是包含空闲页面的,因此直接分配即可。分配出的页面,需要从free list中删除。

2.若Buffer Pool的free list中不存在空闲页面,同时当前系统正在进行BUF_FLUSH_LRU类型的flush (buf_pool->init_flush[BUF_FLUSH_LRU] == TRUE),则等待flush完成,然后跳转到1重新分配。

3.遍历LRU链表,寻找其中的非脏页面,将这些页面从LRU链表中摘除,并插入free list (buf_LRU_scan_and_free_block ->buf_LRU_free_from_common_LRU_list)。从LRU链表的尾部开始向前遍历,第一次遍历srv_LRU_scan_depth = 1024个page,之后每次遍历整个LRU链表。

buf_LRU_free_from_common_LRU_list函数从LRU链表的尾部遍历,对于遇到的每一个page,调用函数buf_LRU_free_block尝试释放当前page。buf_LRU_free_block是一个极为复杂的函数,其简要流程如下:

a)         判断page是否被pin在内存中(buf_page_can_relocate()),若page被pin住,则说明不能释放此page,直接返回。

 

b)         判断page是否是脏页(bpage->oldest_modification),若page为脏页,则说明不能释放此page,直接返回。

 

c)         不满足以上条件,page可以释放。首先将page从LRU list与buf pool的page hash中删除(buf_LRU_block_remove_hashed_page)。

 

d)         判断当前page的类型,若为zip page,需要做特殊处理,此处暂时略过不表。

 

e)          page中的记录,若存储在btr_search_sys->hash_index中,则从此hash表中删除(btr_search_drop_page_hash_index())。在调用此函数时,需要释放buf pool的mutex(buf_pool_mutex_exit()),考虑到同时有多个线程可以同时调用此函数,并且将此page标识为可替换page。因此,在释放buf pool mutex之前,将page标识为pin在内存中,不可被替换(buf_page_set_sticky())。

备注:

此处的hash表是MySQL数据库InnoDB存储引擎提供的adaptive hash index,记录级别的hash index (存储记录项到索引页面的映射)。通过参数innodb_adaptive_hash_index,可以控制是否使用此hash index。若参数设置为true,那么在做scan查询时(btr_cur_search_to_nth_level),就会调用btr_search_guess_on_hash函数就行hash index的查询,若存在则可以直接定位到对应的叶页面,减少了search path的代价。

 

关于InnoDB adaptvie hash index,后续将会做更为详尽的分析,此处简单说明其功能,页面被替换出去时,需要将页面中的记录从hash index中删除。除此之外,adaptive hash index系统中只有一个,使用全局的btr_search_latch来保护,因此存在着并发性能问题

 

f)         在page中的所有记录,均从btr_search_sys hash表中删除之后,重新获取buf pool mutex。并将page上的pin标识删除。然后将page添加到buf pool的free链表中(buf_LRU_block_free_hashed_page -> buf_LRU_block_free_non_file_page)。

 

g)         至此,一个clean page的释放过程完成。

 

h)           同时,InnoDB存储引擎还做了一个统计,当page是clean,可以被选择为替换page,同时判断page是否被访问过(buf_page_is_accessed)。若page未被访问过,那么增加统计计数buf_pool->stat.n_ra_pages_evicted。表示当前page是一个read ahead page,但是却是无效的read ahead。

 

4.若步骤3,遍历LRU链表,成功释放一个clean page,那么再次扫描buf pool的free list,就会找到空闲page,加以利用。成功返回即可。

5.若步骤3未成功释放一个clean page,则继续进行下一个动作:从LRU链表中flush一个dirty page,然后释放此page (buf_flush_single_page_from_LRU())。注意,此时由于处于用户线程之中,因此只flush一个page即可,保证用户的响应时间。

a)      从LRU链表的尾部遍历,找寻第一个可以立即被flush的page (buf_flush_ready_for_flush)。可以立即被flush的page需要满足以下要求:脏页,在FLUSH链表中;未被pin在内存中;

 

b)      若找到一个可以立即flush的page,则调用函数buf_flush_page将当前page写到磁盘中,同时设置flush type为BUF_FLUSH_SINGLE_PAGE。buf_flush_page函数中,主要需要将page pin在内存中(buf_page_set_io_fix(BUF_IO_WRITE));设置buf pool对应flush类型的数量;最后调用buf_flush_write_block_low函数写出page。

 

c)     一个被写出的dirty page,在写出之前,需要做一些额外处理,包括:将此page的最新修改的lsn (newest_lsn)写到页头/页尾;重新计算页面的页头checksum (buf_calc_page_new_checksum)与页尾checksum (buf_calc_page_old_checksum)。

 

d)      注意 页头checksum的计算,需要读取整个页面中的所有数据(一次读取1 byte);页尾checksum的计算,只需要读取页面的前26 bytes即可。而且,需要先计算页头checksum,写入page,然后再计算页尾checksum。在MySQL 5.6.3之后,InnoDB提供了新的checksum计算方法(crc32),同时新增了系统参数innodb_checksum_algorithm来设置不同的checksum算法。

 

e)     前面的buf_flush_page函数负责将page写出到disk,但是并不保证page一定被写到磁盘之上。仍旧需要调用函数buf_flush_sync_datafiles将写fsync到磁盘之上。

 

f)     当写出完全flush到磁盘之后,需要重新在buf pool的LRU链表中定位page,因为在写page时,会释放buf pool mutex以及页面mutex,因此当写完成之后,不能再继续使用原有的page内存结构,可能已经被其他的线程释放。

 

g)     重新定位到page之后,再次调用buf_LRU_free_block函数,判断当前page是否可以被从LRU链表中摘除。

 

6.buf_flush_single_page_from_LRU函数返回,无论是否成功flush一个dirty page,均再次尝试一个新的分配free page的循环。当完成20次循环之后,若仍旧不能找到一个可用的page,打出warnning,要求管理员增加buf pool的大小。

 

7.    疑问:在buf_flush_single_page_from_LRU中,flush一个page时,何时将page从LRU链表与flush链表中摘除?

a)       MySQL数据库InnoDB存储引擎中,无论是日志文件/数据文件的读写操作,当完成之后,会调用buf_page_io_complete函数(异步读/写操作,由后台的io_handler_thread线程组处理;同步读/写操作,由发起读/写操作的线程本身处理)。针对于dirty page的写操作,此函数进而调用buf_flush_write_complete函数,将page从flush链表中摘除(buf_flush_remove);设置in_flush_list为false;同时将页面的oldest_modification设置为0,标识当前页面为clean page,位于LRU链表中的clean page,可以被上面提到的流程重用。

 

b)         如果当前dirty page是有FLUSH LRU完成写出的,那么同时将flush后的clean page移到LRU链表的最后(LRU_old尾部),如此一来,保证下次页面分配可以快速重用此page。

 

c)         最后,无论是那种写操作,写成功之后,增加buf pool的统计信息:buf_pool->stat.n_pages_written++

 

8.当有free page可以使用之后,就可以读取外存页面到内存buf pool之中。前面提到buf_page_io_complete处理写操作的流程,那么该函数又是如何处理读操作的呢?

a)         若读到的是一个压缩页面,则将页面解压(buf_zip_decompress)

 

b)         调用buf_page_is_corrupted函数,判断页面是否已损坏。该函数主要的流程,就是重新计算页面的new checksum与old checksum,然后对比计算出来的checksum与页面中保存的checksum是否一致。若两个checksum均一致,则页面未损坏,否则返回页面已损坏。

 

c)         调用ibuf_merge_or_delete_for_page方法,若页面存在insert buffer记录,则合并。

 

d)       最后,释放页面上的latch与mutex,并返回。

d)         最后,释放页面上的latch与mutex,并返回。

建议继续学习:

  1. Buffer和cache的区别是什么?    (阅读:6839)
  2. Linux操作系统中内存buffer和cache的区别    (阅读:5317)
  3. 快速预热Innodb Buffer Pool的方法    (阅读:3985)
  4. MySQL数据库InnoDB存储引擎 Buffer pool LRU List Flush策略详解    (阅读:3791)
  5. MySQL数据库InnoDB存储引擎 Insert Buffer实现机制详解    (阅读:3536)
  6. InnoDB之Dirty Page、Redo log    (阅读:3440)
  7. grep 命令的buffer选项    (阅读:3067)
  8. 小心grep 的buffer    (阅读:3019)
  9. HBase如何合理设置客户端Write Buffer    (阅读:2728)
  10. 浅谈数据库系统中的cache    (阅读:2463)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1