简单的记录下,在MySQL5.6.12中innodb层的3点跟性能相关的改进
1.在文件操作部分,移除了许多sleep操作,而是改用condition wait
对应的bug http://bugs.mysql.com/bug.php?id=68588。 在Mark的测试中,有近一倍的性能提升
http://bazaar.launchpad.net/~mysql/mysql-server/5.6/revision/4981
主要修改都几种在函数fil_flush中:
每个文件结构体node都增加了一个event:
fil_node_create: node->sync_event = os_event_create(); fil_node_free: os_event_free(node->sync_event)
fil_flush:
当文件上已经有线程在做flush时:
5638 if (node->n_pending_flushes > 0) { 5639 /* We want to avoid calling os_file_flush() on 5640 the file twice at the same time, because we do 5641 not know what bugs OS's may contain in file 5642 i/o */ 5643 5644 ib_int64_t sig_count = 5645 os_event_reset(node->sync_event); 5646 5647 mutex_exit(&fil_system->mutex); 5648 5649 os_event_wait_low(node->sync_event, sig_count); 5650 5651 mutex_enter(&fil_system->mutex); 5652 5653 if (node->flush_counter >= old_mod_counter) { 5654 5655 goto skip_flush; 5656 } 5657 5658 goto retry; 5659 } 5661 ut_a(node->open); 5662 file = node->handle; 5663 node->n_pending_flushes++; 5664 5665 mutex_exit(&fil_system->mutex); 5666 5667 os_file_flush(file); 5668 5669 mutex_enter(&fil_system->mutex); 5670 5671 os_event_set(node->sync_event); 5672 5673 node->n_pending_flushes--;
好吧,我承认我摘录上述代码的目的,只是简单记录下innodb condition wait的用法…
2.用户线程在查找空闲block,会刷单个page,这可能导致sync所有的文件
http://bazaar.launchpad.net/~mysql/mysql-server/5.6/revision/4955
http://bugs.mysql.com/bug.php?id=68658
用户线程做了single page flush后,加入一个IO异步请求队列后,会调用buf_flush_sync_datafiles.随后会唤醒IO线程,并在之后fsync所有的数据文件。
该bzr主要修改包括:
* 所有batch flush操作异步进行(和以前一样)
buf_dblwr_flush_buffered_writes:
914 for (ulint i = 0; i < first_free; i++) {
915 buf_dblwr_write_block_to_datafile(
916 buf_dblwr->buf_block_arr[i], false); // false表示异步写
917 }
* single page flush以寻找一个空闲块,这是同步操作
buf_flush_single_page_from_LRU->buf_flush_page->buf_flush_write_block_low->buf_dblwr_write_single_page
1130 /* We know that the write has been flushed to disk now
1131 and during recovery we will find it in the doublewrite buffer
1132 blocks. Next do the write to the intended position. */
1133 buf_dblwr_write_block_to_datafile(bpage, sync); //该backtrace的sync为TRUE
buf_flush_page在多处被调用到,其增加了一个sync参数,用于表示该page flush操作需要同步还是异步进行。而在5.6.11中会在调用buf_flush_page后调用buf_flush_sync_datafiles()来sync所有的数据文件。
另外,有一种情况,在做SINGLE PAGE FLUSH时,采用异步的方式,backtrace如下:
row_import_for_mysql->buf_LRU_remove_pages->buf_flush_dirty_pages->buf_flush_or_remove_pages->buf_flush_page
这其实就是5.6的新特性ibd import功能的backtrace,后面单独开篇介绍
* 将single page flush的dblwr slot处理转移到IO线程
函数:buf_dblwr_update
当IO操作完成写一个page后,这个函数会被调用到,在5.6.12中会做两件事儿:
对于batch flush操作(BUF_FLUSH_LIST 或者BUF_FLUSH_LRU),当预留给batch flush的slot都全部完成刷新后(buf_dblwr->b_reserved = 0),会去sync数据文件(fil_flush_file_spaces(FIL_TABLESPACE)),将batch_running设为false,并发送完成信号
对于single page flush操作,找到当前page的slot,然后将其设置为未使用(in_use[i] = false),随后发送condition 信号(buf_dblwr->s_event)
这里采用顺序遍历,来寻找当前page的slot,是否存在效率问题?
而在5.6.11版本中,只考虑了batch flush操作。
* 移除对dblwr buffer中的sleep,改用condition wait
写double write buffer时,如果已经在刷dblwr,以前是sleep 10ms,现在改成condition wait了,这里包括batch flush 和single page flush,这两者都增加了条件变量
当脏页刷新非常频繁时,会看到很明显的性能提升
*其他
另外一个没提到的修改是函数buf_flush_LRU_tail
在5.6.11的版本中,并没有对buf_flush_LRU的返回值进行处理。而在5.6.12中,增加了如下逻辑:
2092 for (ulint j = 0; 2093 j < scan_depth; 2094 j += PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE) { 2095 2096 ulint n_flushed = 0; 2097 2098 /* Currently page_cleaner is the only thread 2099 that can trigger an LRU flush. It is possible 2100 that a batch triggered during last iteration is 2101 still running, */ 2102 if (buf_flush_LRU(buf_pool, 2103 PAGE_CLEANER_LRU_BATCH_CHUNK_SIZE, 2104 &n_flushed)) { 2105 2106 /* Allowed only one batch per 2107 buffer pool instance. */ 2108 buf_flush_wait_batch_end( 2109 buf_pool, BUF_FLUSH_LRU); 2110 } 2111 2112 if (n_flushed) { 2113 total_flushed += n_flushed; 2114 } else { 2115 /* Nothing to flush */ 2116 break; 2117 } 2118 }
3.优化batch flush的效率,之前的时间复杂度为O(N*N)
bug:http://bugs.mysql.com/bug.php?id=69170
http://bazaar.launchpad.net/~mysql/mysql-server/5.6/revision/4980
每个buffer pool实例增加了一个新的变量:
const buf_page_t* flush_list_hp;/*!< “hazard pointer”
used during scan of flush_list
while doing flush list batch.
Protected by flush_list_mutex */
根据注释,其在批量刷新时使用,用flush_list_mutex 来保护
buf_flush_set_hp :设置flush_list_hp指针,指向参数传递的page
buf_flush_update_hp:当flush_list上的block移除或者移动时,需要检查buf_flush_set_hp指针是否被其他正在扫描flush list的线程设置,如果flush_list_hp指向我们下一个将要扫描的page,则将其设置为NULL,表示需要重新扫描
有两个地方会调用到这个函数:
* 从flush list上移除一个page的时候(buf_flush_remove)
* 为flush list上的一个page重分配控制块,buf_flush_relocate_on_flush_list
buf_do_flush_list_batch:
在该函数的修改是核心部分,主要消除了在bug#69170中描述的o(n*n)的时间复杂度。所有作用于flush list的线程,都需要先检查flush_list_hp指针,
这里的方法很简单,从flush list的尾部开始扫描, 每次获取一个page后,将bp->flush_list_hp的指针指向该page的前一个, 然后释放bp->flush_list_mutex
然后执行该page的刷新
flushed = buf_flush_page_and_try_neighbors(
bpage, BUF_FLUSH_LIST, min_n, &count);
再次持有flush_list_mutex锁,查看bp->flush_list_hp是否发生变化,如果发生变化了,则表明该指针被其他线程设置了,也就是说,有其他线程对flush list做了操作,因此需要从flush list尾部重新开始扫描
在5.6.11的版本中,总是无条件的从尾部开始重新扫描。