日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

腾讯云数据库团队:MySQL AHI 实现解析

發(fā)布時(shí)間:2023/12/20 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 腾讯云数据库团队:MySQL AHI 实现解析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

MySQL 定位用戶記錄的過(guò)程可以描述為:打開(kāi)索引 -> 根據(jù)索引鍵值逐層查找 B+ 樹(shù) branch 結(jié)點(diǎn) -> 定位到葉子結(jié)點(diǎn),將 cursor 定位到滿足條件的 rec 上;如果樹(shù)高為 N, 則需要讀取索引樹(shù)上的 N 個(gè)結(jié)點(diǎn)并進(jìn)行比較,如果 buffer_pool 較小,則大量的操作都會(huì)在 pread 上,用戶響應(yīng)時(shí)間變長(zhǎng);另外,MySQL中 Server 層與 Engine 之間的是以 row 為單位進(jìn)行交互的,engine 將記錄返回給 server 層,server 層對(duì) engine 的行數(shù)據(jù)進(jìn)行相應(yīng)的計(jì)算,然后緩存或發(fā)送至客戶端,為了減少交互過(guò)程所需要的時(shí)間,MySQL 做了兩個(gè)優(yōu)化:

  • 如果同一個(gè)查詢(xún)語(yǔ)句連續(xù)取出了 MYSQL_FETCH_CACHE_THRESHOLD(4) 條記錄,則會(huì)調(diào)用函數(shù) row_sel_enqueue_cache_row_for_mysql 將 MYSQL_FETCH_CACHE_SIZE(8) 記錄緩存至 prebuilt->fetch_cache 中,在隨后的 prebuilt->n_fetch_cached 次交互中,都會(huì)從prebuilt->fetch_cache 中直接取數(shù)據(jù)返回到 server 層,那么問(wèn)題來(lái)了,即使是用戶只需要 4 條數(shù)據(jù),Engine 層也會(huì)將 MYSQL_FETCH_CACHE_SIZE 條數(shù)據(jù)放入 fetch_cache 中,造成了不必要的緩存使用。另外, 5.7 可以根據(jù)用戶的設(shè)置來(lái)調(diào)整緩存用戶記錄的條數(shù);

  • Engine 取出數(shù)據(jù)后,會(huì)將 cursor 的位置保存起來(lái),當(dāng)取下一條數(shù)據(jù)時(shí),會(huì)嘗試恢復(fù) cursor 的位置,成功則并繼續(xù)取下一條數(shù)據(jù),否則會(huì)重新定位 cursor 的位置,從而通過(guò)保存 cursor 位置的方法可以減少 server 層 & engine 層交互的時(shí)間;

Server 層 & engine 層交互的過(guò)程如下,由于 server & engine 的 row format 不一樣,那么 engine row format -> server row format 在讀場(chǎng)景下的開(kāi)銷(xiāo)也是比較大的。

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 */ }復(fù)制代碼

AHI 功能作用

由以上的分析可以看到 MySQL 一次定位 cursor 的過(guò)程即是從根結(jié)點(diǎn)到葉子結(jié)點(diǎn)的路徑,時(shí)間復(fù)雜度為:height(index) + [CPU cost time],上述的兩個(gè)優(yōu)化過(guò)程無(wú)法省略定位 cursor 的中間結(jié)點(diǎn),因此需要引入一種可以從 search info 定位到葉子結(jié)點(diǎn)的方法,從而省略根結(jié)點(diǎn)到葉子結(jié)點(diǎn)的路徑上所消耗的時(shí)間,而這種方法即是 自適應(yīng)索引(Adaptive hash index, AHI)。查詢(xún)語(yǔ)句使用 AHI 的時(shí)候有以下優(yōu)點(diǎn):

  • 可以直接通過(guò)從查詢(xún)條件直接定位到葉子結(jié)點(diǎn),減少一次定位所需要的時(shí)間;
  • 在 buffer pool 不足的情況下,可以只針對(duì)熱點(diǎn)數(shù)據(jù)頁(yè)建立緩存,從而避免數(shù)據(jù)頁(yè)頻繁的 LRU;

但是 AHI 并不總能提升性能,在多表Join & 模糊查詢(xún) & 查詢(xún)條件經(jīng)常變化的情況下,此時(shí)系統(tǒng)監(jiān)控 AHI 使用的資源大于上述的好處時(shí),不僅不能發(fā)揮 AHI 的優(yōu)點(diǎn),還會(huì)為系統(tǒng)帶來(lái)額外的 CPU 消耗,此時(shí)需要將 AHI 關(guān)閉來(lái)避免不必要的系統(tǒng)資源浪費(fèi),關(guān)于 AHI 的適應(yīng)場(chǎng)景可以參考:mysql_adaptive_hash_index_implementation

AHI 內(nèi)存結(jié)構(gòu)

AHI 會(huì)監(jiān)控查詢(xún)語(yǔ)句中的條件并進(jìn)行分析(稍后會(huì)進(jìn)行詳細(xì)的介紹),當(dāng)滿足 AHI 緩存建立的條件后,會(huì)選擇索引的若干前綴索引列對(duì)熱點(diǎn)數(shù)據(jù)頁(yè)組建 hash page 以記錄 hash value -> page block 之間的對(duì)應(yīng)關(guān)系, 本小節(jié)主要對(duì) AHI 的內(nèi)存結(jié)構(gòu) & 內(nèi)存來(lái)源進(jìn)行相應(yīng)的介紹,其內(nèi)存結(jié)構(gòu)如圖:

上圖是 AHI 的一個(gè)內(nèi)存結(jié)構(gòu)示意圖,AHI 主要使用以下兩種內(nèi)存:

  • 系統(tǒng)初始化分配的 hash_table 的內(nèi)存,其中每一個(gè) hash_table 的數(shù)組大小為:(buf_pool_get_curr_size() / sizeof(void*) / 64),根據(jù)機(jī)器位數(shù)的不同,數(shù)組大小不同, 32位機(jī)器為 buffer_pool大小的 1/256, 64 位機(jī)器為 buffer_pool 大小的 1/512, 此部分內(nèi)存為系統(tǒng)內(nèi)存(mem_area_alloc->malloc),主要用于構(gòu)建 hash_table 結(jié)構(gòu);
#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)); 復(fù)制代碼
  • 當(dāng) AHI 對(duì)數(shù)據(jù)頁(yè)面構(gòu)造 AHI 緩存時(shí),此時(shí)使用 buffer_pool 的 free 鏈接中的內(nèi)存,即 buffer_pool 的內(nèi)存,所以在頁(yè)數(shù)據(jù)發(fā)生變化的時(shí)候,需要對(duì) AHI 緩存進(jìn)行相應(yīng)的維護(hù);

AHI 實(shí)現(xiàn)解析

【 AHI 在查詢(xún)過(guò)程中的作用范圍 】

MySQL 中 Server & Innodb 的交互中是以行為單位進(jìn)行交互的,Innodb 逐行取數(shù)據(jù)的過(guò)程可以分為以下 6 個(gè)步驟:

  • 0.如果發(fā)現(xiàn)其它線程需要對(duì)btr_search_latch上鎖,則釋放 btr_search_latch,然后執(zhí)行 1; (5.6 & 5.7 在實(shí)現(xiàn)上不同)
  • 1.嘗試從 row_prebuilt_t->fetch_cache 中取數(shù)據(jù)庫(kù)記錄,有則直接返回,如果沒(méi)有數(shù)據(jù)或者不可以使用 fetch cache, 則執(zhí)行2;
  • 2.在滿足條件的情況下,使用 AHI 定位 cursor 位置并返回?cái)?shù)據(jù), 否則執(zhí)行 3;
  • 3.根據(jù) direction 的值確認(rèn)是否可以從 row_prebuilt_t中恢復(fù) cursor 的位置,如果 direction = 0 或不可以從 row_prebuilt_t中恢復(fù) cursor 的位置, 則調(diào)用 btr_pcur_open_at_index_side 打開(kāi)索引,調(diào)用 btr_cur_search_to_nth_level,如果可以使用 AHI,則快速定位葉子結(jié)點(diǎn),否則遍歷 height(index) 個(gè)結(jié)點(diǎn)定位 cursor, 然后進(jìn)入 4;如果可以從 row_prebuilt_t 恢復(fù)則執(zhí)行 5;
  • 4.根據(jù)查找的值在葉子結(jié)點(diǎn)中逐個(gè)匹配,查找滿足條件的記錄,返回?cái)?shù)據(jù),取下一條記錄時(shí)執(zhí)行 3,5;
  • 5.移動(dòng) cursor 到下一條記錄并返回?cái)?shù)據(jù);

AHI 則在第 [2, 3] 兩個(gè)步驟中影響著定位葉子結(jié)點(diǎn)的過(guò)程,根據(jù)查詢(xún)條件定位葉子節(jié)點(diǎn)的過(guò)程中發(fā)揮著 hash 的作用,AHI 的實(shí)現(xiàn)主要包括 AHI 初始化過(guò)程、構(gòu)建條件、使用過(guò)程、維護(hù)過(guò)程、系統(tǒng)監(jiān)控等部分,我們從源碼的實(shí)現(xiàn)的角度上分析上述過(guò)程。

【 AHI 初始化過(guò)程 】

AHI 作為 buffer_pool 的一部分,是建立查詢(xún)條件與 REC 在內(nèi)存中位置的一個(gè) hash_table, 在系統(tǒng)啟動(dòng)的時(shí)候會(huì)隨著 buffer_pool 的初始化而自動(dòng)的建立相應(yīng)的內(nèi)存結(jié)構(gòu),其初始化過(guò)程為:

  • 利用系統(tǒng)內(nèi)存 (malloc) 創(chuàng)建全局變量 btr_search_sys 及其鎖結(jié)構(gòu);
  • 利用系統(tǒng)內(nèi)存 (malloc) 建立 hash_table 內(nèi)存結(jié)構(gòu),并初始化其成員變量,其中 hash_table 數(shù)組的大小取決于當(dāng)前 buffer_pool 的 size 與 系統(tǒng)的機(jī)器位數(shù),計(jì)算公式為:buf_pool_get_curr_size() / sizeof(void*) / 64,hash_table_t 的結(jié)構(gòu)如下所示:
(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 } 復(fù)制代碼

說(shuō)明:

  • 所有 buffer_pool instances 共享一個(gè) AHI, 而不是每一個(gè) buffer_pool instance 一個(gè) AHI;
  • 5.7.8 之前 AHI 只有一個(gè)全局的鎖結(jié)構(gòu) btr_search_latch, 當(dāng)壓力比較大的時(shí)候會(huì)出現(xiàn)性能瓶頸,5.7.8 對(duì) AHI 進(jìn)行了拆鎖處理,詳情可以參考函數(shù): btr_get_search_table() & btr_search_sys_create()
  • AHI 的 btr_search_latch (bug#62018) & index lock 是MySQL中兩個(gè)比較大的鎖,詳情可以參考 Index lock and adaptive search – next two biggest InnoDB problems,5.7 通過(guò)對(duì) AHI 鎖拆分 (5.7 commit id: ab17ab91) 以及引入不同的索引鎖協(xié)議 (WL#6326) 解決了這兩個(gè)問(wèn)題;

【 AHI 構(gòu)建條件 】

AHI 是建立在 search info & REC 內(nèi)存地址之間的映射信息,在系統(tǒng)接受訪問(wèn)之前并沒(méi)有足夠的信息來(lái)建立 AHI 的映射信息,所以需要搜集 SQL 語(yǔ)句在執(zhí)行過(guò)程中的 search_info & block info 信息并判斷是否可以為數(shù)據(jù)頁(yè)建立 AHI 緩存,其中:

search info 對(duì)應(yīng) btr_search_t, 用于記錄 index 中的 n_fields (前綴索引列數(shù)) & n_bytes(last column bytes) 信息,這些被用于計(jì)算 fold 值;

block info 用于記錄計(jì)算 fold 的值所需要的 fields & bytes 之外,還記錄了在此情況下使用 AHI 在此數(shù)據(jù)頁(yè)上潛在成功的次數(shù);

我們簡(jiǎn)單的對(duì) AHI 統(tǒng)計(jì)信息的幾個(gè)方面進(jìn)行簡(jiǎn)單的描述。

  • 觸發(fā) AHI 索引統(tǒng)計(jì)的條件

SQL 語(yǔ)句在定位 cursor 的過(guò)程中會(huì)執(zhí)行 btr_cur_search_to_nth_level 函數(shù),當(dāng)打開(kāi) AHI 的時(shí)候,在btr_cur_search_to_nth_level 返回之前會(huì)調(diào)用 btr_search_info_update 來(lái)更新相應(yīng)的統(tǒng)計(jì)信息,如果當(dāng)前的索引的 serch_info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS (17),則對(duì) search info & block info 不進(jìn)行統(tǒng)計(jì),否則則會(huì)調(diào)用 btr_search_info_update_slow 更新 search info & block info 信息,實(shí)現(xiàn)如下:

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); } 復(fù)制代碼
  • AHI 中索引查詢(xún)信息 (index->search_info) 的更新與自適應(yīng)的過(guò)程

背景知識(shí): btr_cur_search_to_nth_level 中在定位 cursor 的過(guò)程中會(huì)在樹(shù)的每一層調(diào)用 page_cur_search_with_match 來(lái)確定下一個(gè) branch 結(jié)點(diǎn)或葉子結(jié)點(diǎn),page_cur_search_with_match 函數(shù)會(huì)將查詢(xún)過(guò)程中比較的前綴索引列數(shù) & 最后一列匹配的字節(jié)數(shù)記錄至 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match},目的是為了保存與 search tuple 在比較過(guò)程時(shí)的最小比較單元,詳細(xì)的計(jì)算過(guò)程可以參考 page_cur_search_with_match 的實(shí)現(xiàn)代碼。

首先判斷當(dāng)前 index 是否為 insert buffer tree, 如果是 insert buffer, 則不進(jìn)行 AHI 等相關(guān)的操作;

其次,如果當(dāng)前索引的 info->n_hash_potential = 0,則會(huì)按照推薦算法從 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match} 推薦出前綴索引列數(shù) & 最后一列的字節(jié)數(shù)用于計(jì)算 AHI 中存儲(chǔ)的鍵 {ha_node_t->fold} 的值。

當(dāng) info->n_hash_potential != 0 時(shí),則會(huì)判斷當(dāng)前查詢(xún)匹配模式 & index->search_info 中保存的匹配模式是否發(fā)生變化,如果沒(méi)有發(fā)生變化,則會(huì)增加此模式下潛在利用 AHI 成功的次數(shù) (info->n_hash_potential),否則需要重新推薦前綴索引列等相關(guān)信息,并清空 info->n_hash_potential 的值(info->n_hash_potential = 0),AHI 就是利用這種方法來(lái)實(shí)現(xiàn)自適應(yīng)的,所以在打開(kāi) AHI 的系統(tǒng)中不建議經(jīng)常變換查詢(xún)條件,前綴索引等信息的計(jì)算過(guò)程如下:

btr_search_info_update_hash {.../* We have to set a new recommendation; skip the hash analysisfor a while to avoid unnecessary CPU time usage when there is nochance 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;} } 復(fù)制代碼

由以上算法可以看出,選擇{info->n_fields, info->n_bytes, info->left_side}的依據(jù)則是在不超過(guò) unique index 列數(shù)的前提下,使其計(jì)算代價(jià)最小,而 index->info->left_side 的值則會(huì)決定存儲(chǔ)同一數(shù)據(jù)頁(yè)上相同前綴索引的最左記錄還是最右記錄。

  • 數(shù)據(jù)頁(yè) block 信息的更新

數(shù)據(jù)頁(yè) block info 的更新主要包括數(shù)據(jù)頁(yè)上的索引匹配模式、在已有索引匹配模式下成功的次數(shù)以及是否為該數(shù)據(jù)頁(yè)建立 AHI 緩存信息的判斷,其主要過(guò)程如下:

  • 將 index->info->last_hash_succ 設(shè)置為 FALSE, 此時(shí)其它線程無(wú)法使用該索引上 AHI 功能;

  • 如果 index->search_info 的匹配格式 & 該數(shù)據(jù)頁(yè)上保存的匹配模式相同時(shí),則增加此 block 使用 AHI 成功的次數(shù) block->n_hash_helps, 如果已經(jīng)為該數(shù)據(jù)頁(yè)建立 AHI 緩存,則設(shè)置 index->info->last_hash_succ = TRUE;

  • 如果 index->search_info 的匹配格式 & 該數(shù)據(jù)頁(yè)上保存的匹配模式不相同,則設(shè)置 block->n_hash_helps=1 且使用 index->search_info 對(duì) block 上的索引匹配信息進(jìn)行重新設(shè)置,詳細(xì)過(guò)程可參考 btr_search_update_block_hash_info;

  • 判斷是否需要為數(shù)據(jù)頁(yè)建立 AHI 緩存,在數(shù)據(jù)頁(yè) block 上使用 AHI 成功的次數(shù)大于此數(shù)據(jù)頁(yè)上用戶記錄的 1/16 且當(dāng)前前綴索引的條件下使用 AHI 成功的次數(shù)大于 100 時(shí), 如果此數(shù)據(jù)頁(yè)使用 AHI 潛在成功的次數(shù)大于 2 倍該數(shù)據(jù)頁(yè)上的用戶記錄或者當(dāng)前推薦的前綴索引信息發(fā)生了變化的時(shí),則需要為數(shù)據(jù)頁(yè)構(gòu)造 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);}} 復(fù)制代碼

    【 AHI 構(gòu)建過(guò)程(收集 & 判斷 & 建立)】

    AHI 的構(gòu)建過(guò)程指的是根據(jù) index->search_info 構(gòu)建查詢(xún)條件 & 數(shù)據(jù)頁(yè)的 hash 關(guān)系,其主要過(guò)程為:

  • 收集 hash 信息。遍歷該數(shù)據(jù)頁(yè)上的所有用戶記錄,建立由前綴索引信息 & 物理記錄之間的映射關(guān)系的數(shù)組 {folds, recs},其中 index->info->left_side 用來(lái)判斷在前綴索引列相同情況下如何保存物理頁(yè)記錄,從代碼中可以得知:當(dāng) left_side 為 TRUE 時(shí)前綴索引列相同的記錄只保存最左記錄,當(dāng) left_side 為 FALSE 時(shí)前綴索引列相同的記錄只保存最右記錄,代碼實(shí)現(xiàn)如下:
  • 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;} 復(fù)制代碼
  • 如果之前該數(shù)據(jù)頁(yè)已經(jīng)存在 AHI 緩存信息但前綴索引信息與當(dāng)前的信息不一致,則釋放之前緩存的 AHI 信息,如果釋放超過(guò)了一個(gè) page size,則將釋放的數(shù)據(jù)頁(yè)退還給 buffer_pool->free 鏈表;

  • 調(diào)用 btr_search_check_free_space_in_heap 來(lái)確保 AHI 有足夠的內(nèi)存生成映射信息 ha_node_t {fold, data, next},該內(nèi)存從 buffer_pool->free 鏈表獲得,詳情參考:buf_block_alloc(), fold 的值的計(jì)算可參考函數(shù):rec_fold();

  • 由于操作過(guò)程中釋放了 btr_search_latch,需要再次檢查 block 上的AHI信息是否發(fā)生了變化,如果發(fā)生變化則退出函數(shù);

  • 調(diào)用 ha_insert_for_fold 方法將之前收集的信息生成 ha_node_t, 并將其存放到 btr_search_sys->hash_table 的數(shù)組中,其中存放后的結(jié)構(gòu)可以參考圖 AHI memory structure;

  • for (i = 0; i < n_cached; i++) {ha_insert_for_fold(table, folds[i], block, recs[i]);} 復(fù)制代碼

    【 AHI 使用條件及定位葉子結(jié)點(diǎn)過(guò)程 】


    “AHI 在查詢(xún)過(guò)程中的作用范圍” 一節(jié)中我們?cè)敿?xì)的介紹了 MySQL 中 Server 層 & engine 層中的交互方式以及 AHI 在整個(gè)過(guò)程中的位置 & 作用,下面著要看一下在 步驟 2, 3 中 AHI 是如何工作的。

    步驟 2 中,是使用 AHI 的一種 shortcut 查詢(xún)方式,只有在滿足很苛刻的條件后才能使用 AHI 的 shortcut 查詢(xún)方式,這些苛刻條件包括:

  • 當(dāng)前索引是 cluster index;

  • 當(dāng)前查詢(xún)是 unique search;

  • 當(dāng)前查詢(xún)不包含 blob 類(lèi)型的大字段;

  • 記錄長(zhǎng)度不能大于 page_size/8;

  • 不是使用 memcache 接口協(xié)議的查詢(xún);

  • 事物開(kāi)啟且隔離級(jí)別大于 READ UNCOMMITTED;

  • 簡(jiǎn)單 select 查詢(xún)而非在 function & procedure;

  • 在滿足以上條件后才能使用 AHI 的 shortcut 查詢(xún)方式定位葉子結(jié)點(diǎn),5.7 中滿足條件后的操作可以簡(jiǎn)單的描述為:

    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)); 復(fù)制代碼

    步驟 3 中使用 AHI 快速定位葉子結(jié)點(diǎn)同樣需要滿足一些條件,具體可以參考代碼:btr_cur_search_to_nth_level(),在此不再累述,我們著重分析一下使用 AHI 定位葉子節(jié)點(diǎn)的過(guò)程。

  • 對(duì) index 所在的 hash_table 上鎖,使用查詢(xún)條件中的 tuple 信息計(jì)算出鍵值 fold;
  • rw_lock_s_lock(btr_search_get_latch(index)); fold = dtuple_fold(tuple, cursor->n_fields, cursor->n_bytes, index_id); 復(fù)制代碼
  • 在 hash_table 上進(jìn)行查找 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); 復(fù)制代碼
  • 釋放鎖資源并根據(jù)返回的記錄定位葉子結(jié)點(diǎn);
  • block = buf_block_align(rec); rw_lock_s_unlock(btr_search_get_latch(index)); btr_cur_position(index, (rec_t*) rec, block, cursor); 復(fù)制代碼
  • 定位到葉子結(jié)點(diǎn)后的過(guò)程和不使用 AHI 之后的過(guò)程類(lèi)似,直接返回記錄并記錄 cursor 位置;
  • AHI 維護(hù) & 監(jiān)控

    MySQL 5.7 中有兩個(gè) AHI 相關(guān)的參數(shù),分別為:innodb_adaptive_hash_index, innodb_adaptive_hash_index_parts,其中 innodb_adaptive_hash_index 為動(dòng)態(tài)調(diào)整的參數(shù),用以控制是否打開(kāi) AHI 功能;innodb_adaptive_hash_index_parts 是只讀參數(shù),在實(shí)例運(yùn)行期間是不能修改,用于調(diào)整 AHI 分區(qū)的個(gè)數(shù)(5.7.8 引入),減少鎖沖突,詳細(xì)介紹可以參考官方說(shuō)明:innodb_adaptive_hash_index, innodb_adaptive_hash_index,本節(jié)主要介紹操作 AHI 的相關(guān)命令以及命令的內(nèi)部實(shí)現(xiàn)過(guò)程。

  • 打開(kāi) AHI 操作 & 內(nèi)部實(shí)現(xiàn)
  • set global innodb_adaptive_hash_index=ON,此命令只是對(duì)全局變量進(jìn)行設(shè)置,代碼實(shí)現(xiàn)如下:

    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(); } 復(fù)制代碼
  • 關(guān)閉 AHI 操作 & 內(nèi)部實(shí)現(xiàn)
  • set global innodb_adaptive_hash_index= OFF,此命令用于關(guān)閉 AHI 功能,具體實(shí)現(xiàn)可參考 btr_search_disable(), 關(guān)閉流程說(shuō)明:

    • 設(shè)置 btr_search_enabled = FALSE,關(guān)閉 AHI 功能;
    • 將數(shù)據(jù)字典中所有緩存的表對(duì)象的 ref_count 設(shè)置為0,只有 btr_search_info_get_ref_count(info, index) = 0 的情況下才能清除數(shù)據(jù)字典中的緩存對(duì)象,詳情見(jiàn) dict_table_can_be_evicted();
    • 將所有數(shù)據(jù)頁(yè)中的統(tǒng)計(jì)信息置空,具體實(shí)現(xiàn)可參考 buf_pool_clear_hash_index();
    • 釋放 AHI 所使用的 buffer_pool 的內(nèi)存,btr_search_disable 具體實(shí)現(xiàn)如下:
    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 inthe 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(); }復(fù)制代碼
  • AHI 緩存信息的維護(hù)
  • AHI 維護(hù)的是 search info & REC 在物理內(nèi)存地址的 hash 關(guān)系,當(dāng)物理記錄的位置或者所在 block 的地址發(fā)生變化時(shí),AHI 也需要對(duì)其進(jìn)行相應(yīng)的維護(hù),如新記錄的的插入,表記錄的的刪除,數(shù)據(jù)頁(yè)的分裂,drop table & alter table,LRU 換頁(yè)等都需要對(duì) AHI 進(jìn)行相應(yīng)的維護(hù),詳情可參考函數(shù) btr_search_update_hash_ref() & btr_search_drop_page_hash_index() & buf_LRU_drop_page_hash_for_tablespace()的實(shí)現(xiàn);

  • AHI 信息的監(jiān)控
  • AHI 默認(rèn)情況下只對(duì) adaptive_hash_searches (使用 AHI 方式查詢(xún)的次數(shù)) & adaptive_hash_searches_btree (使用 bree 查詢(xún)的次數(shù),需要遍歷 branch 結(jié)點(diǎn)) 進(jìn)行監(jiān)控,更詳細(xì)的監(jiān)控需要進(jìn)行額外的設(shè)置,詳細(xì)設(shè)置方法可參考 innodb_monitor_enable & module_adaptive_hash ,打開(kāi) AHI 的監(jiān)控方法、使用監(jiān)控、重置監(jiān)控的方法如下:

    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) 復(fù)制代碼

    值得一提的是只有執(zhí)行 set global innodb_monitor_reset_all='adaptive_hash_%' & set global innodb_monitor_disable='adaptive_hash%' 才對(duì)狀態(tài)進(jìn)行重置,如果發(fā)現(xiàn) adaptive_hash_searches << adaptive_hash_searches_btree 的時(shí)候,則應(yīng)該關(guān)閉 AHI 以減少不必要的系統(tǒng)消耗。

    相關(guān)推薦 MySQL數(shù)據(jù)庫(kù)的高可用性分析 騰訊云數(shù)據(jù)庫(kù)CDB for MySQL相關(guān)文檔 MySQL開(kāi)發(fā)實(shí)踐8問(wèn),你能hold住幾個(gè)?


    此文已由作者授權(quán)騰訊云技術(shù)社區(qū)發(fā)布,轉(zhuǎn)載請(qǐng)注明文章出處,獲取更多云計(jì)算技術(shù)干貨,可請(qǐng)前往騰訊云技術(shù)社區(qū) 微信公眾號(hào):騰訊云技術(shù)社區(qū)( QcloudCommunity)

    總結(jié)

    以上是生活随笔為你收集整理的腾讯云数据库团队:MySQL AHI 实现解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。