MySQL5.6.12的Innodb效能改進

zhaiwx_yinfeng發表於2016-05-10
簡單的記錄下,在MySQL5.6.12中innodb層的3點跟效能相關的改進
1.在檔案操作部分,移除了許多sleep操作,而是改用condition wait
對應的bug http://bugs.mysql.com/bug.php?id=68588。 在Mark的測試中,有近一倍的效能提升
主要修改都幾種在函式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所有的檔案
使用者執行緒做了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)
每個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的版本中,總是無條件的從尾部開始重新掃描。

 


相關文章