這兩天正在把percona5.5的multi ahi更新檔port到5.6.11,順便過了下ahi相關的代碼邏輯,是以寫的比較散亂,慎入!!
以下的分析基于5.6.11原版,主要包括ahi的存儲結構,以及在檢索btree時如何使用ahi,
ahi的初始化發生在innodb啟動時,在為buffer pool配置設定完記憶體後(buf_pool_init),建立ahi相關結構體,配置其所需要的記憶體:
btr_search_sys_create(buf_pool_get_curr_size() / sizeof(void*) / 64);
記憶體占用預設不超過buffer pool大小的64分之一
ahi的初始化主要包括以下幾個重要的對象:
btr_search_latch_temp 讀寫鎖對象,但外部使用的是btr_search_latch:
#define btr_search_latch (*btr_search_latch_temp)
btr_search_sys,ahi的全局控制結構體,隻有一個成員:
hash_table_t* hash_index;
建立哈希表
其他相關結構體:
每個索引對象為維護的index->search_info,類型為btr_search_t,其成員包括:
ulint ref_count
該index上被ahi索引的block數量,需要通過btr_search_latch保護
buf_block_t* root_guess
上次擷取的root page
ulint hash_analysis
當該值超過btr_search_hash_analysis時,才會去更新ahi的entry.
這主要是為了避免太過頻繁的更新ahi(預設超過17次查詢後,才會去嘗試更新ahi)
在為search info重設新的n_fields和n_bytes時,會重設該字段為0
ibool last_hash_succ
如果上次的搜尋成功了,并使用了ahi,則設定為true
ulint n_hash_potential
使用ahi持續成功檢索的次數.
在函數btr_search_guess_on_hash中,當成功使用了一次ahi後,如果目前n_hash_potential<btr_search_build_limit+5=105,則将n_hash_potential++
在函數btr_search_info_update_hash中,有兩種情況會遞增該變量:
a.info->n_fields >= n_unique && cursor->up_match >= n_unique //目前推薦的n_fields 和cursor->up_match能夠唯一決定一個記錄
b.當info->left_side為true時,info->n_fields, info->n_bytes需要小于大于cursor的n_fields和n_bytes,當為false時,前者大于後者。這兩種情況下,也會遞增n_hash_potential
在需要更新search info時,該變量可能會被重置
ulint n_fields
推薦的比對列的個數
ulint n_bytes
推薦的第一個部分比對列的位元組數
ibool left_side
布爾變量,其值取決于多個有相同字首的最左邊的記錄是否應該被索引
block控制結構體上相關變量(buf_block_t)
ulint n_hash_helps
用于控制是否為該block建立新的ahi項,在函數btr_search_update_block_hash_info中被遞增或重置。當block上的n_fields,n_bytes以及left_side和info上的對應值相同時,才可能去遞增該變量;
當n_hash_helps大于該page記錄總數的1/16分之一時,才可能為該該block上的全部使用者記錄建立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); //傳回true表示需要為該page上所有使用者記錄建ahi
}
}
當重新為該block建立ahi後(btr_search_build_page_hash_index),block->n_hash_helps被重置為0
ulint n_fields
該變量,及下面兩個變量,在兩個函數中被設定
btr_search_move_or_delete_hash_entries,用于當諸如發生索引分裂時,将老block上的curr_* 指派給新block,并為其建立ahi
btr_search_update_block_hash_info 将info的對應變量指派給block
block->n_hash_helps = 1;
block->n_fields = info->n_fields;
block->n_bytes = info->n_bytes;
block->left_side = info->left_side;
隻有當上述這幾個值和info對應的變量在一段時間内保持一緻,才會去考慮為該block上的全部使用者記錄建立ahi
ulint n_bytes
同上
ibool left_side
unsigned curr_n_fields:10
該變量及以下兩個變量,隻在在該block全部使用者記錄建立ahi時(btr_search_build_page_hash_index)才會被設定
unsigned curr_n_bytes:15
unsigned curr_left_side:1
dict_index_t* index
該block對應的索引指針,在為該block建立ahi時被指派
cursor結構體btr_cur_t (懶得翻譯,注釋的很清楚)
ulint up_match
if the search mode was page_cur_le, the number of matched fields to the the first user record to the right of
the cursor record after btr_cur_search_to_nth_level;
for the mode page_cur_ge, the matched fields to the first user record at the
cursor or to the right of it;
note that the up_match and low_match values may exceed the correct values for comparison to the adjacent user
record if that record is on a different leaf page! (see the note in row_ins_duplicate_error_in_clust.)
ulint up_bytes
number of matched bytes to the right at the time cursor positioned;
only used internally in searches: not defined after the search
ulint low_match
if search mode was page_cur_le, the number of matched fields to the first user record at the cursor or to the left of it after btr_cur_search_to_nth_level;
not defined for page_cur_ge or any other search modes; see also the note in up_match!
ulint low_bytes
ulint n_fields
用于在檢索ahi時指派
ulint n_bytes
ulint fold
查詢ahi時,根據tuple計算的hash key值
btr_cur_search_to_nth_level -> btr_search_guess_on_hash
隻有滿足如下條件,才會進入函數:
主要函數btr_search_guess_on_hash:
a.滿足如下任一情況直接傳回
a1)info->n_hash_potential = 0時,直接傳回false
a2)
cursor->n_fields = info->n_fields;
cursor->n_bytes = info->n_bytes;
目前檢索的列的個數小于推薦的列個數時,表示使用者提供的列的值的個數,不足以進行ahi檢索,是以直接傳回(dtuple_get_n_fields(tuple) < cursor->n_fields + (cursor->n_bytes > 0))
b.根據目前tuple建構fold,并去檢索ahi
将cursor定位到該記錄
btr_cur_position(index, (rec_t*) rec, block, cursor);
c.檢查記錄的有效性(btr_search_check_guess):
btr_search_check_guess函數流程如下:
c1)擷取記錄的
match表示比對的完整列的個數,bytes表示第一個部分比對的位元組數
随後設定cursor->up_match 和cursor->low_match,主要根據tuple和ahi中的記錄的比較結果來區分
c2)
當查詢模式為page_cur_ge(>=)時,如果cmp =1,表示tuple比ahi的記錄要大,傳回false,否則設定cursor->up_match = match;如果match >= n_unique,直接傳回true
當查詢模式為page_cur_le時,如果cmp = -1,表示tuple比ahi的記錄要小,傳回false,否則設定cursor->low_match = match
當查詢模式為page_cur_g時, 如果cmp != -1,表示tuple>=ahi的記錄,傳回false;
當查詢模式為page_cur_l時,如果cmp != 1,表示tuple <= ahi的記錄,傳回false
當查詢模式為page_cur_ge 或者page_cur_g時,找到目前從ahi讀取的記錄的前一個記錄,并跟tuple進行對比,當查詢模式為page_cur_ge時,隻有tuple大于prev_rec時,才傳回true,而對于page_cur_g,隻有當tuple>= prev_rec時,才傳回true
當查詢模式為page_cur_le或者page_cur_l時,找到目前從ahi讀取的記錄的下一條記錄,并跟tuple進行對比, 當查詢模式為page_cur_le時,隻有tuple <prev_prec時,才傳回true,并設定cursor->up_match = match;而對于page_cur_l,隻有當tuple<=prev_rec時,才傳回true
通過上述比較,實際上也可以借助ahi來優化範圍查詢
d.
成功讀取一個ahi記錄,設定last_hash_succ為true(info->last_hash_succ = true)
如果失敗,則設定:
cursor->flag = btr_cur_hash_fail;
info->last_hash_succ = false;
在函數btr_cur_search_to_nth_level 中,當完成了搜尋之後,如果最終定位的層是葉子節點,才去調用btr_search_info_update:
注意,如果我們成功使用ahi 獲得了記錄位置,就不會來到這個邏輯,而是直接從btr_cur_search_to_nth_level函數傳回。
1.info->hash_analysis++ ,當info->hash_analysis < btr_search_hash_analysis(預設為17)時,直接從函數傳回,不做任何更新。
這主要是為了避免頻繁的更新ahi所帶來的開銷
2.btr_search_info_update_slow
>更新目前索引的 search info( btr_search_info_update_hash(info, cursor))
a.當滿足如下條件時,需要設定新的recommand值
1)info->n_hash_potential = 0
2)
3)設定新的recommand值
當cmp為0時,設定:
否則根據cmp是大于0還是小于0 ,設定info->n_fields 和info->n_bytes的值,并設定info->n_hash_potential = 1,具體判斷邏輯為:
b.當滿足如下條件時,info->n_hash_potential++,并直接從函數傳回
1)info->n_fields >= n_unique && cursor->up_match >= n_unique
>判斷是否需要在這個block上建立一個新的索引entry (build_index = btr_search_update_block_hash_info(info, block, cursor) )
傳回true表示需要建立
>當需要為page建立ahi(build_index 不為空,或者cursor->flag == btr_cur_hash_fail),檢查hash index中是否有足夠的空間(btr_search_check_free_space_in_heap)
>cursor->flag == btr_cur_hash_fail 表示之前使用ahi檢索失敗,則調用btr_search_update_hash_ref來更新目前curosr對應記錄的ahi
btr_cur_hash_fail 表示使用ahi檢索失敗了,需要持有btr_search_latch的x鎖的情況下,進行如下判斷
block->index為null,表明未為該block建立ahi,直接傳回
>如果需要建立一個新的entry(即函數btr_search_update_block_hash_info傳回true)
btr_search_build_page_hash_index函數用于為目前page上的所有記錄建立ahi索引
a.當block上有老的不同的ahi記錄時,将其删除掉:
b.
c.
讀取目前block對應page上的所有使用者記錄,建立ahi項
fold = rec_fold(rec, offsets, n_fields, n_bytes, index->id);
對于left_side為true還是false有不同的選擇,
例如一個page上有如下記錄
1 2
2 2
3 5
4 5
5 7
6 8
如果block->left_side為true,存儲的記錄為
1,3,5,6
如果為false,存儲的記錄為:
2, 4,5,6
left_side決定了當遇到相同記錄字首時,是選擇最左邊的還是最右邊的記錄。
d.找到需要建立ahi的記錄後,
檢查是否有空閑空間 btr_search_check_free_space_in_heap()
擷取btr_search_latch的x鎖後,再次檢查:
如果這是第一次為該block建立ahi,index->search_info->ref_count++,ref_count表示這個索引上有多少個block建立了ahi
然後依次将之前搜集的記錄插入到ahi中(ha_insert_for_fold)
3.執行dml時更新ahi,沒啥好說的,代碼邏輯非常簡單,就是更新對應的hash表記錄
btr_search_update_hash_on_insert
btr_search_update_hash_node_on_insert
btr_search_update_hash_on_delete