騰訊雲技術分享:MySQL AHI 實現解析

MySQL 定位用戶記錄的過程能夠描述爲:打開索引 -> 根據索引鍵值逐層查找 B+ 樹 branch 結點 -> 定位到葉子結點,將 cursor 定位到知足條件的 rec 上;若是樹高爲 N, 則須要讀取索引樹上的 N 個結點並進行比較,若是 buffer_pool 較小,則大量的操做都會在 pread 上,用戶響應時間變長;另外,MySQL中 Server 層與 Engine 之間的是以 row 爲單位進行交互的,engine 將記錄返回給 server 層,server 層對 engine 的行數據進行相應的計算,而後緩存或發送至客戶端,爲了減小交互過程所須要的時間,MySQL 作了兩個優化:javascript

  • 若是同一個查詢語句連續取出了 MYSQL_FETCH_CACHE_THRESHOLD(4) 條記錄,則會調用函數 row_sel_enqueue_cache_row_for_mysql 將 MYSQL_FETCH_CACHE_SIZE(8) 記錄緩存至 prebuilt->fetch_cache 中,在隨後的 prebuilt->n_fetch_cached 次交互中,都會從prebuilt->fetch_cache 中直接取數據返回到 server 層,那麼問題來了,即便是用戶只須要 4 條數據,Engine 層也會將 MYSQL_FETCH_CACHE_SIZE 條數據放入 fetch_cache 中,形成了沒必要要的緩存使用。另外, 5.7 能夠根據用戶的設置來調整緩存用戶記錄的條數;php

  • Engine 取出數據後,會將 cursor 的位置保存起來,當取下一條數據時,會嘗試恢復 cursor 的位置,成功則並繼續取下一條數據,不然會從新定位 cursor 的位置,從而經過保存 cursor 位置的方法能夠減小 server 層 & engine 層交互的時間;html

   Server 層 & engine 層交互的過程以下,因爲 server & engine 的 row format 不同,那麼 engine row format -> server row format 在讀場景下的開銷也是比較大的。java

while (rc == NESTED_LOOP_OK && join->return_tab >= join_tab)
{
    int error;
    if (in_first_read)
    {    
      in_first_read= false;
      error= (*join_tab->read_first_record)(join_tab);
    }    
    else 
      error= info->read_record(info);           /* load data from engine */

    rc= evaluate_join_record(join, join_tab);   /* computed by server */
}

AHI 功能做用

    由以上的分析能夠看到 MySQL 一次定位 cursor 的過程便是從根結點到葉子結點的路徑,時間複雜度爲:height(index) + [CPU cost time],上述的兩個優化過程沒法省略定位 cursor 的中間結點,所以須要引入一種能夠從 search info 定位到葉子結點的方法,從而省略根結點到葉子結點的路徑上所消耗的時間,而這種方法便是 自適應索引(Adaptive hash index, AHI)。查詢語句使用 AHI 的時候有如下優勢:node

  • 能夠直接經過從查詢條件直接定位到葉子結點,減小一次定位所須要的時間;
  • 在 buffer pool 不足的狀況下,能夠只針對熱點數據頁創建緩存,從而避免數據頁頻繁的 LRU;

    可是 AHI 並不總能提高性能,在多表Join & 模糊查詢 & 查詢條件常常變化的狀況下,此時系統監控 AHI 使用的資源大於上述的好處時,不只不能發揮 AHI 的優勢,還會爲系統帶來額外的 CPU 消耗,此時須要將 AHI 關閉來避免沒必要要的系統資源浪費,關於 AHI 的適應場景能夠參考:mysql_adaptive_hash_index_implementationmysql

AHI 內存結構

    AHI 會監控查詢語句中的條件並進行分析(稍後會進行詳細的介紹),當知足 AHI 緩存創建的條件後,會選擇索引的若干前綴索引列對熱點數據頁組建 hash page 以記錄 hash value -> page block 之間的對應關係, 本小節主要對 AHI 的內存結構 & 內存來源進行相應的介紹,其內存結構如圖:算法

上圖是 AHI 的一個內存結構示意圖,AHI 主要使用如下兩種內存:sql

  • 系統初始化分配的 hash_table 的內存,其中每個 hash_table 的數組大小爲:(buf_pool_get_curr_size() / sizeof(void*) / 64),根據機器位數的不一樣,數組大小不一樣, 32位機器爲 buffer_pool大小的 1/256, 64 位機器爲 buffer_pool 大小的 1/512, 此部份內存爲系統內存(mem_area_alloc->malloc),主要用於構建 hash_table 結構;
#0  mem_area_alloc (psize=0x7fffffff9888, pool=0x19c27c0) at ../storage/innobase/mem/mem0pool.cc:380
#1  0x0000000000bafb00 in mem_heap_create_block_func (heap=0x0, n=72, file_name=0x10bd7f8 "../storage/innobase/ha/hash0hash.cc", line=303, type=0)
    at ../storage/innobase/mem/mem0mem.cc:336
#2  0x0000000000d91c3a in mem_heap_create_func (n=72, file_name=0x10bd7f8 "../storage/innobase/ha/hash0hash.cc", line=303, type=0) at ../storage/innobase/include/mem0mem.ic:449
#3  0x0000000000d91d78 in mem_alloc_func (n=72, file_name=0x10bd7f8 "../storage/innobase/ha/hash0hash.cc", line=303, size=0x0) at ../storage/innobase/include/mem0mem.ic:537
#4  0x0000000000d9358b in hash0_create (n=16352) at ../storage/innobase/ha/hash0hash.cc:303
#5  0x0000000000d8f699 in ha_create_func (n=16352, sync_level=0, n_sync_obj=0, type=3) at ../storage/innobase/ha/ha0ha.cc:67
#6  0x0000000000cfeaff in btr_search_sys_create (hash_size=16352) at ../storage/innobase/btr/btr0sea.cc:179
#7  0x0000000000d099f9 in buf_pool_init (total_size=8388608, n_instances=1) at ../storage/innobase/buf/buf0buf.cc:1498
...
(gdb) n
381                     return(malloc(*psize));
  • 當 AHI 對數據頁面構造 AHI 緩存時,此時使用 buffer_pool 的 free 連接中的內存,即 buffer_pool 的內存,因此在頁數據發生變化的時候,須要對 AHI 緩存進行相應的維護;

AHI 實現解析

【 AHI 在查詢過程當中的做用範圍 】

    MySQL 中 Server & Innodb 的交互中是以行爲單位進行交互的,Innodb 逐行取數據的過程能夠分爲如下 6 個步驟:數據庫

  • 0.若是發現其它線程須要對btr_search_latch上鎖,則釋放 btr_search_latch,而後執行 1; (5.6 & 5.7 在實現上不一樣)
  • 1.嘗試從 row_prebuilt_t->fetch_cache 中取數據庫記錄,有則直接返回,若是沒有數據或者不可使用 fetch cache, 則執行2
  • 2.在知足條件的狀況下,使用 AHI 定位 cursor 位置並返回數據, 不然執行 3
  • 3.根據 direction 的值確認是否能夠從 row_prebuilt_t中恢復 cursor 的位置,若是 direction = 0 或不能夠從 row_prebuilt_t中恢復 cursor 的位置, 則調用 btr_pcur_open_at_index_side 打開索引,調用 btr_cur_search_to_nth_level,若是可使用 AHI,則快速定位葉子結點,不然遍歷 height(index) 個結點定位 cursor, 而後進入 4;若是能夠從 row_prebuilt_t 恢復則執行 5
  • 4.根據查找的值在葉子結點中逐個匹配,查找知足條件的記錄,返回數據,取下一條記錄時執行 3,5
  • 5.移動 cursor 到下一條記錄並返回數據;

    AHI 則在第 [2, 3] 兩個步驟中影響着定位葉子結點的過程,根據查詢條件定位葉子節點的過程當中發揮着 hash 的做用,AHI 的實現主要包括 AHI 初始化過程、構建條件、使用過程、維護過程、系統監控等部分,咱們從源碼的實現的角度上分析上述過程。數組

【 AHI 初始化過程 】

    AHI 做爲 buffer_pool 的一部分,是創建查詢條件與 REC 在內存中位置的一個 hash_table, 在系統啓動的時候會隨着 buffer_pool 的初始化而自動的創建相應的內存結構,其初始化過程爲:

  • 利用系統內存 (malloc) 建立全局變量 btr_search_sys 及其鎖結構;
  • 利用系統內存 (malloc) 創建 hash_table 內存結構,並初始化其成員變量,其中 hash_table 數組的大小取決於當前 buffer_pool 的 size 與 系統的機器位數,計算公式爲:buf_pool_get_curr_size() / sizeof(void*) / 64,hash_table_t 的結構以下所示:
(gdb) p table
$37 = (hash_table_t *) 0x1aabfc8
(gdb) p *table
$38 = {
  type = HASH_TABLE_SYNC_NONE, 
  adaptive = 0, 
  n_cells = 0, 
  array = 0x0, 
  n_sync_obj = 0, 
  sync_obj = {
    mutexes = 0x0, 
    rw_locks = 0x0
  }, 
  heaps = 0x0, 
  heap = 0x0, 
  magic_n = 0
}

說明:

  • 全部 buffer_pool instances 共享一個 AHI, 而不是每個 buffer_pool instance 一個 AHI;
  • 5.7.8 以前 AHI 只有一個全局的鎖結構 btr_search_latch, 當壓力比較大的時候會出現性能瓶頸,5.7.8 對 AHI 進行了拆鎖處理,詳情能夠參考函數: btr_get_search_table() & btr_search_sys_create()
  • AHI 的 btr_search_latch (bug#62018) & index lock 是MySQL中兩個比較大的鎖,詳情能夠參考 Index lock and adaptive search – next two biggest InnoDB problems,5.7 經過對 AHI 鎖拆分 (5.7 commit id: ab17ab91) 以及引入不一樣的索引鎖協議 (WL#6326) 解決了這兩個問題;

【 AHI 構建條件 】

    AHI 是創建在 search info & REC 內存地址之間的映射信息,在系統接受訪問以前並無足夠的信息來創建 AHI 的映射信息,因此須要蒐集 SQL 語句在執行過程當中的 search_info & block info 信息並判斷是否能夠爲數據頁創建 AHI 緩存,其中:

    search info 對應 btr_search_t, 用於記錄 index 中的 n_fields (前綴索引列數) & n_bytes(last column bytes) 信息,這些被用於計算 fold 值;

    block info 用於記錄計算 fold 的值所須要的 fields & bytes 以外,還記錄了在此狀況下使用 AHI 在此數據頁上潛在成功的次數;

   咱們簡單的對 AHI 統計信息的幾個方面進行簡單的描述。

  • 觸發 AHI 索引統計的條件

    SQL 語句在定位 cursor 的過程當中會執行 btr_cur_search_to_nth_level 函數,當打開 AHI 的時候,在btr_cur_search_to_nth_level 返回以前會調用 btr_search_info_update 來更新相應的統計信息,若是當前的索引的 serch_info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS (17),則對 search info & block info 不進行統計,不然則會調用 btr_search_info_update_slow 更新 search info & block info 信息,實現以下:

void btr_search_info_update(
/*===================*/
  dict_index_t* index,  /*!< in: index of the cursor */
  btr_cur_t*  cursor) /*!< in: cursor which was just positioned */
{
 ...
  info->hash_analysis++;
  if (info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS) {
    /* Do nothing */
  return;
  }
  btr_search_info_update_slow(info, cursor);
}
  • AHI 中索引查詢信息 (index->search_info) 的更新與自適應的過程

背景知識: btr_cur_search_to_nth_level 中在定位 cursor 的過程當中會在樹的每一層調用 page_cur_search_with_match 來肯定下一個 branch 結點或葉子結點,page_cur_search_with_match 函數會將查詢過程當中比較的前綴索引列數 & 最後一列匹配的字節數記錄至 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match},目的是爲了保存與 search tuple 在比較過程時的最小比較單元,詳細的計算過程能夠參考 page_cur_search_with_match 的實現代碼。

    首先判斷當前 index 是否爲 insert buffer tree, 若是是 insert buffer, 則不進行 AHI 等相關的操做;

    其次,若是當前索引的 info->n_hash_potential = 0,則會按照推薦算法從 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match} 推薦出前綴索引列數 & 最後一列的字節數用於計算 AHI 中存儲的鍵 {ha_node_t->fold} 的值。

    當 info->n_hash_potential != 0 時,則會判斷當前查詢匹配模式 & index->search_info 中保存的匹配模式是否發生變化,若是沒有發生變化,則會增長此模式下潛在利用 AHI 成功的次數 (info->n_hash_potential),不然須要從新推薦前綴索引列等相關信息,並清空 info->n_hash_potential 的值(info->n_hash_potential = 0),AHI 就是利用這種方法來實現自適應的,因此在打開 AHI 的系統中不建議常常變換查詢條件,前綴索引等信息的計算過程以下:

btr_search_info_update_hash
{
  ...

  /* We have to set a new recommendation; skip the hash analysis
  for a while to avoid unnecessary CPU time usage when there is no
  chance for success */

  info->hash_analysis = 0; 

  cmp = ut_pair_cmp(cursor->up_match, cursor->up_bytes,
        cursor->low_match, cursor->low_bytes);
  if (cmp == 0) { 
    info->n_hash_potential = 0; 

    /* For extra safety, we set some sensible values here */

    info->n_fields = 1; 
    info->n_bytes = 0; 

    info->left_side = TRUE;

  } else if (cmp > 0) { 
    info->n_hash_potential = 1; 

    if (cursor->up_match >= n_unique) {

      info->n_fields = n_unique;
      info->n_bytes = 0; 

    } else if (cursor->low_match < cursor->up_match) {

      info->n_fields = cursor->low_match + 1; 
      info->n_bytes = 0; 
    } else {
      info->n_fields = cursor->low_match;
      info->n_bytes = cursor->low_bytes + 1; 
    }    

    info->left_side = TRUE;
  } else {
    info->n_hash_potential = 1; 

    if (cursor->low_match >= n_unique) {

      info->n_fields = n_unique;
      info->n_bytes = 0; 

    } else if (cursor->low_match > cursor->up_match) {

      info->n_fields = cursor->up_match + 1; 
      info->n_bytes = 0; 
    } else {
      info->n_fields = cursor->up_match;
      info->n_bytes = cursor->up_bytes + 1; 
    }    

    info->left_side = FALSE;
  }
}

    由以上算法能夠看出,選擇{info->n_fields, info->n_bytes, info->left_side}的依據則是在不超過 unique index 列數的前提下,使其計算代價最小,而 index->info->left_side 的值則會決定存儲同一數據頁上相同前綴索引的最左記錄仍是最右記錄。

  • 數據頁 block 信息的更新

數據頁 block info 的更新主要包括數據頁上的索引匹配模式、在已有索引匹配模式下成功的次數以及是否爲該數據頁創建 AHI 緩存信息的判斷,其主要過程以下:

  1. 將 index->info->last_hash_succ 設置爲 FALSE, 此時其它線程沒法使用該索引上 AHI 功能;

  2. 若是 index->search_info 的匹配格式 & 該數據頁上保存的匹配模式相同時,則增長此 block 使用 AHI 成功的次數 block->n_hash_helps, 若是已經爲該數據頁創建 AHI 緩存,則設置 index->info->last_hash_succ = TRUE;

  3. 若是 index->search_info 的匹配格式 & 該數據頁上保存的匹配模式不相同,則設置 block->n_hash_helps=1 且使用 index->search_info 對 block 上的索引匹配信息進行從新設置,詳細過程可參考 btr_search_update_block_hash_info

  4. 判斷是否須要爲數據頁創建 AHI 緩存,在數據頁 block 上使用 AHI 成功的次數大於此數據頁上用戶記錄的 1/16 且當前前綴索引的條件下使用 AHI 成功的次數大於 100 時, 若是此數據頁使用 AHI 潛在成功的次數大於 2 倍該數據頁上的用戶記錄或者當前推薦的前綴索引信息發生了變化的時,則須要爲數據頁構造 AHI 緩存信息,詳情可參考如下代碼;

if ((block->n_hash_helps > page_get_n_recs(block->frame)
       / BTR_SEARCH_PAGE_BUILD_LIMIT)
      && (info->n_hash_potential >= BTR_SEARCH_BUILD_LIMIT)) {

    if ((!block->index)
        || (block->n_hash_helps > 2 * page_get_n_recs(block->frame))
        || (block->n_fields != block->curr_n_fields)
        || (block->n_bytes != block->curr_n_bytes)
        || (block->left_side != block->curr_left_side)) {

      /* Build a new hash index on the page */

      return(TRUE);
    }
  }

【 AHI 構建過程(收集 & 判斷 & 創建)】

    AHI 的構建過程指的是根據 index->search_info 構建查詢條件 & 數據頁的 hash 關係,其主要過程爲:

  1. 收集 hash 信息。遍歷該數據頁上的全部用戶記錄,創建由前綴索引信息 & 物理記錄之間的映射關係的數組 {folds, recs},其中 index->info->left_side 用來判斷在前綴索引列相同狀況下如何保存物理頁記錄,從代碼中能夠得知:當 left_side 爲 TRUE 時前綴索引列相同的記錄只保存最左記錄,當 left_side 爲 FALSE 時前綴索引列相同的記錄只保存最右記錄,代碼實現以下:
for (;;) {
    next_rec = page_rec_get_next(rec);
    if (page_rec_is_supremum(next_rec)) {
      if (!left_side) {
        folds[n_cached] = fold;
        recs[n_cached] = rec;
        n_cached++;
      }
      break;
    }

    offsets = rec_get_offsets(next_rec, index, offsets,
            n_fields + (n_bytes > 0), &heap);
    next_fold = rec_fold(next_rec, offsets, n_fields,
             n_bytes, index->id);

    if (fold != next_fold) {
      /* Insert an entry into the hash index */
      if (left_side) {
        folds[n_cached] = next_fold;
        recs[n_cached] = next_rec;
        n_cached++;
      } else {
        folds[n_cached] = fold;
        recs[n_cached] = rec;
        n_cached++;
      }
    }

    rec = next_rec;
    fold = next_fold;
  }
  1. 若是以前該數據頁已經存在 AHI 緩存信息但前綴索引信息與當前的信息不一致,則釋放以前緩存的 AHI 信息,若是釋放超過了一個 page size,則將釋放的數據頁退還給 buffer_pool->free 鏈表;

  2. 調用 btr_search_check_free_space_in_heap 來確保 AHI 有足夠的內存生成映射信息 ha_node_t {fold, data, next},該內存從 buffer_pool->free 鏈表得到,詳情參考:buf_block_alloc(), fold 的值的計算可參考函數:rec_fold();

  3. 因爲操做過程當中釋放了 btr_search_latch,須要再次檢查 block 上的AHI信息是否發生了變化,若是發生變化則退出函數;

  4. 調用 ha_insert_for_fold 方法將以前收集的信息生成 ha_node_t, 並將其存放到 btr_search_sys->hash_table 的數組中,其中存放後的結構能夠參考圖 AHI memory structure;

for (i = 0; i < n_cached; i++) {
    ha_insert_for_fold(table, folds[i], block, recs[i]);
  }

【 AHI 使用條件及定位葉子結點過程 】


    在 「AHI 在查詢過程當中的做用範圍」 一節中咱們詳細的介紹了 MySQL 中 Server 層 & engine 層中的交互方式以及 AHI 在整個過程當中的位置 & 做用,下面着要看一下在 步驟 2, 3 中 AHI 是如何工做的。

    步驟 2 中,是使用 AHI 的一種 shortcut 查詢方式,只有在知足很苛刻的條件後才能使用 AHI 的 shortcut 查詢方式,這些苛刻條件包括:

  1. 當前索引是 cluster index;

  2. 當前查詢是 unique search;

  3. 當前查詢不包含 blob 類型的大字段;

  4. 記錄長度不能大於 page_size/8;

  5. 不是使用 memcache 接口協議的查詢;

  6. 事物開啓且隔離級別大於 READ UNCOMMITTED;

  7. 簡單 select 查詢而非在 function & procedure;

在知足以上條件後才能使用 AHI 的 shortcut 查詢方式定位葉子結點,5.7 中知足條件後的操做能夠簡單的描述爲:

rw_lock_s_lock(btr_get_search_latch(index));
...
row_sel_try_search_shortcut_for_mysql()
...
rw_lock_s_lock(btr_get_search_latch(index));

    步驟 3 中使用 AHI 快速定位葉子結點一樣須要知足一些條件,具體能夠參考代碼:btr_cur_search_to_nth_level(),在此再也不累述,咱們着重分析一下使用 AHI 定位葉子節點的過程。

  1. 對 index 所在的 hash_table 上鎖,使用查詢條件中的 tuple 信息計算出鍵值 fold;
rw_lock_s_lock(btr_search_get_latch(index));
fold = dtuple_fold(tuple, cursor->n_fields, cursor->n_bytes, index_id);
  1. 在 hash_table 上進行查找 key = fold 的 ha_node_t;
const rec_t*
ha_search_and_get_data(
/*===================*/
  hash_table_t* table,  /*!< in: hash table */
  ulint   fold) /*!< in: folded value of the searched data */
{
  ha_node_t*  node;
  hash_assert_can_search(table, fold);
  ut_ad(btr_search_enabled);

  node = ha_chain_get_first(table, fold);

  while (node) {
    if (node->fold == fold) {
      return(node->data);
    }
    node = ha_chain_get_next(node);
  }
  return(NULL);
}

rec = (rec_t*) ha_search_and_get_data(btr_search_get_hash_table(index), fold);
  1. 釋放鎖資源並根據返回的記錄定位葉子結點;
block = buf_block_align(rec);
rw_lock_s_unlock(btr_search_get_latch(index));
btr_cur_position(index, (rec_t*) rec, block, cursor);
  1. 定位到葉子結點後的過程和不使用 AHI 以後的過程相似,直接返回記錄並記錄 cursor 位置;

AHI 維護 & 監控

    MySQL 5.7 中有兩個 AHI 相關的參數,分別爲:innodb_adaptive_hash_index, innodb_adaptive_hash_index_parts,其中 innodb_adaptive_hash_index 爲動態調整的參數,用以控制是否打開 AHI 功能;innodb_adaptive_hash_index_parts 是隻讀參數,在實例運行期間是不能修改,用於調整 AHI 分區的個數(5.7.8 引入),減小鎖衝突,詳細介紹能夠參考官方說明:innodb_adaptive_hash_index, innodb_adaptive_hash_index,本節主要介紹操做 AHI 的相關命令以及命令的內部實現過程。

  1. 打開 AHI 操做 & 內部實現

set global innodb_adaptive_hash_index=ON,此命令只是對全局變量進行設置,代碼實現以下:

Enable the adaptive hash search system. */
UNIV_INTERN
void
btr_search_enable(void)
/*====================*/
{
  btr_search_x_lock_all();
  btr_search_enabled = TRUE;   /* global variables which indicate whether AHI can be used */
  btr_search_x_unlock_all();
}
  1. 關閉 AHI 操做 & 內部實現

set global innodb_adaptive_hash_index= OFF,此命令用於關閉 AHI 功能,具體實現可參考 btr_search_disable(), 關閉流程說明:

  • 設置 btr_search_enabled = FALSE,關閉 AHI 功能;
  • 將數據字典中全部緩存的表對象的 ref_count 設置爲0,只有 btr_search_info_get_ref_count(info, index) = 0 的狀況下才能清除數據字典中的緩存對象,詳情見 dict_table_can_be_evicted()
  • 將全部數據頁中的統計信息置空,具體實現可參考 buf_pool_clear_hash_index();
  • 釋放 AHI 所使用的 buffer_pool 的內存,btr_search_disable 具體實現以下:
Disable the adaptive hash search system and empty the index. */
UNIV_INTERN
void
btr_search_disable(void)
/*====================*/
{
  dict_table_t* table;
  ulint   i;

  mutex_enter(&dict_sys->mutex);
  btr_search_x_lock_all();

  btr_search_enabled = FALSE;

  /* Clear the index->search_info->ref_count of every index in
  the data dictionary cache. */
  for (table = UT_LIST_GET_FIRST(dict_sys->table_LRU); table;
       table = UT_LIST_GET_NEXT(table_LRU, table)) {

    btr_search_disable_ref_count(table);
  }

  for (table = UT_LIST_GET_FIRST(dict_sys->table_non_LRU); table;
       table = UT_LIST_GET_NEXT(table_LRU, table)) {

    btr_search_disable_ref_count(table);
  }

  mutex_exit(&dict_sys->mutex);

  /* Set all block->index = NULL. */
  buf_pool_clear_hash_index();

  /* Clear the adaptive hash index. */
  for (i = 0; i < btr_search_index_num; i++) {
    hash_table_clear(btr_search_sys->hash_tables[i]);
    mem_heap_empty(btr_search_sys->hash_tables[i]->heap);
  }

  btr_search_x_unlock_all();
}
  1. AHI 緩存信息的維護

    AHI 維護的是 search info & REC 在物理內存地址的 hash 關係,當物理記錄的位置或者所在 block 的地址發生變化時,AHI 也須要對其進行相應的維護,如新記錄的的插入,表記錄的的刪除,數據頁的分裂,drop table & alter table,LRU 換頁等都須要對 AHI 進行相應的維護,詳情可參考函數 btr_search_update_hash_ref() & btr_search_drop_page_hash_index() & buf_LRU_drop_page_hash_for_tablespace()的實現;

  1. AHI 信息的監控

    AHI 默認狀況下只對 adaptive_hash_searches (使用 AHI 方式查詢的次數) & adaptive_hash_searches_btree (使用 bree 查詢的次數,須要遍歷 branch 結點) 進行監控,更詳細的監控須要進行額外的設置,詳細設置方法可參考 innodb_monitor_enable & module_adaptive_hash ,打開 AHI 的監控方法、使用監控、重置監控的方法以下:

MySQL [information_schema]> set global innodb_monitor_enable = module_adaptive_hash;
Query OK, 0 rows affected (0.00 sec)

MySQL [information_schema]> select status, name, subsystem,count, max_count, min_count, avg_count, time_enabled, time_disabled from INNODB_METRICS where subsystem like '%adaptive_hash%';
+---------+------------------------------------------+---------------------+--------+-----------+-----------+--------------------+---------------------+---------------+
| status  | name                                     | subsystem           | count  | max_count | min_count | avg_count          | time_enabled        | time_disabled |
+---------+------------------------------------------+---------------------+--------+-----------+-----------+--------------------+---------------------+---------------+
| enabled | adaptive_hash_searches                   | adaptive_hash_index | 259530 |    259530 |      NULL | 1663.6538461538462 | 2016-12-16 14:03:07 | NULL          |
| enabled | adaptive_hash_searches_btree             | adaptive_hash_index | 143318 |    143318 |      NULL |  918.7051282051282 | 2016-12-16 14:03:07 | NULL          |
| enabled | adaptive_hash_pages_added                | adaptive_hash_index |  14494 |     14494 |      NULL | 127.14035087719299 | 2016-12-16 14:03:49 | NULL          |
| enabled | adaptive_hash_pages_removed              | adaptive_hash_index |      0 |      NULL |      NULL |                  0 | 2016-12-16 14:03:49 | NULL          |
| enabled | adaptive_hash_rows_added                 | adaptive_hash_index | 537933 |    537933 |      NULL |  4718.710526315789 | 2016-12-16 14:03:49 | NULL          |
| enabled | adaptive_hash_rows_removed               | adaptive_hash_index |      0 |      NULL |      NULL |                  0 | 2016-12-16 14:03:49 | NULL          |
| enabled | adaptive_hash_rows_deleted_no_hash_entry | adaptive_hash_index |      0 |      NULL |      NULL |                  0 | 2016-12-16 14:03:49 | NULL          |
| enabled | adaptive_hash_rows_updated               | adaptive_hash_index |      0 |      NULL |      NULL |                  0 | 2016-12-16 14:03:49 | NULL          |
+---------+------------------------------------------+---------------------+--------+-----------+-----------+--------------------+---------------------+---------------+
8 rows in set (0.00 sec)

MySQL [information_schema]> set global innodb_monitor_reset_all='adaptive_hash_%';
Query OK, 0 rows affected (0.00 sec)

MySQL [information_schema]> set global innodb_monitor_disable='adaptive_hash%';
Query OK, 0 rows affected (0.00 sec)

MySQL [information_schema]> select status, name, subsystem,count, max_count, min_count, avg_count, time_enabled, time_disabled from INNODB_METRICS where subsystem like '%adaptive_hash%';
+----------+------------------------------------------+---------------------+-------+-----------+-----------+-----------+--------------+---------------+
| status   | name                                     | subsystem           | count | max_count | min_count | avg_count | time_enabled | time_disabled |
+----------+------------------------------------------+---------------------+-------+-----------+-----------+-----------+--------------+---------------+
| disabled | adaptive_hash_searches                   | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_searches_btree             | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_pages_added                | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_pages_removed              | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_rows_added                 | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_rows_removed               | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_rows_deleted_no_hash_entry | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
| disabled | adaptive_hash_rows_updated               | adaptive_hash_index |     0 |      NULL |      NULL |      NULL | NULL         | NULL          |
+----------+------------------------------------------+---------------------+-------+-----------+-----------+-----------+--------------+---------------+
8 rows in set (0.00 sec)

值得一提的是隻有執行 set global innodb_monitor_reset_all='adaptive_hash_%' & set global innodb_monitor_disable='adaptive_hash%' 纔對狀態進行重置,若是發現 adaptive_hash_searches << adaptive_hash_searches_btree 的時候,則應該關閉 AHI 以減小沒必要要的系統消耗。


相關推薦

MySQL數據庫的高可用性分析

騰訊雲數據庫CDB for MySQL相關文檔

MySQL開發實踐8問,你能hold住幾個?


閱讀原文,本文由騰雲閣首發,更多內容請點擊騰雲閣

相關文章
相關標籤/搜索