MySQL数据库InnoDB存储引擎 Buffer Pool页面分配详解
当用户需要读取一个在外存中的页面,或者是分配一个新的页面进行插入,就需要调用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,并返回。
建议继续学习:
- Buffer和cache的区别是什么? (阅读:6823)
- Linux操作系统中内存buffer和cache的区别 (阅读:5305)
- 快速预热Innodb Buffer Pool的方法 (阅读:3961)
- MySQL数据库InnoDB存储引擎 Buffer pool LRU List Flush策略详解 (阅读:3773)
- MySQL数据库InnoDB存储引擎 Insert Buffer实现机制详解 (阅读:3515)
- InnoDB之Dirty Page、Redo log (阅读:3418)
- grep 命令的buffer选项 (阅读:3051)
- 小心grep 的buffer (阅读:3000)
- HBase如何合理设置客户端Write Buffer (阅读:2715)
- 浅谈数据库系统中的cache (阅读:2452)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:MySQLOPS 数据库与运维自动化技术分享 来源: MySQLOPS 数据库与运维自动化技术分享
- 标签: Buffer 页面分配
- 发布时间:2012-06-05 00:09:47
- [67] Go Reflect 性能
- [67] Oracle MTS模式下 进程地址与会话信
- [67] 如何拿下简短的域名
- [61] IOS安全–浅谈关于IOS加固的几种方法
- [60] 图书馆的世界纪录
- [59] 【社会化设计】自我(self)部分――欢迎区
- [58] android 开发入门
- [56] 视觉调整-设计师 vs. 逻辑
- [49] 给自己的字体课(一)——英文字体基础
- [47] 界面设计速成