關於innodb中查詢的定位方法
原創轉載請註明出處
原始碼版本 5.7.14
涉及原始碼檔案
page0cur.cc page0page.h page0page.cc rem0cmp.cc
為什麼談及定位方法,因為在innodb中,比如一個插入語句我們需要定位在哪裡插入(PAGE_CUR_LE),比如一個查詢語句我們需要定位到其第一個需要讀取資料的位置,因此定位方法是查詢的根本。而找到這個記錄位置後實際上是用一個叫做page_cur_t結構體進行儲存,暫且叫他cursor遊標
struct page_cur_t{ const dict_index_t* index; rec_t* rec; /*!< pointer to a record on page */ ulint* offsets; buf_block_t* block; /*!< pointer to the block containing rec */ };
其中包含了本index的資料字典類容、實際的資料、記錄所在塊的資訊等,下面我具體談一下定位方法,同時結合原始碼來看它具體的實現。
我們先來明確一下概念:
- 記錄(rec):通常為儲存在記憶體中物理記錄的完全複製,通常用一個unsigned char* 指標指向整個記錄
- 元組(dtuple):物理記錄的邏輯體現,他就複雜得多,但是一個記錄(rec)對應一個元組(dtuple),由dtuple_t結構體表示,其中每一個field由一個dfield_t結構體表示,資料儲存在dfied_t的一個叫做void* data的指標中
可自行參考運維內參等其他書籍,這裡就在簡單描述到這裡,本文會出現相關術語。
一、查詢模式(search mode)
在innodb中的常用的search mode有如下幾個
/* Page cursor search modes; the values must be in this order! */ enum page_cur_mode_t { PAGE_CUR_UNSUPP = 0, PAGE_CUR_G = 1, PAGE_CUR_GE = 2, PAGE_CUR_L = 3, PAGE_CUR_LE = 4, };
- PAGE_CUR_G(>)
- PAGE_CUR_GE(>=)
- PAGE_CUR_L(<)
- PAGE_CUR_LE(<=)
我們來討論一個問題考慮如下有序的陣列
1,2,3,3,4,5,6,7
規律1:
如果我們查詢>=3(PAGE_CUR_GE)和<3(PAGE_CUR_L),那麼自然我們需要將位置定位到2到3之間我們且用2-3表示
- 如果是>=3那麼我們需要將記錄定位到3及[3(第一個),正無窮)
-
如果是<3那麼我們需要將記錄定位到2及(負無窮,2]
也就是說>=3和<3定位的區間是相同的2-3
如果我們查詢<=3(PAGE_CUR_LE)和>3(PAGE_CUR_G),那麼自然我們需要將位置定位到3到4之間我們且用3-4表示
- 如果是<=3那麼我們需要將記錄定位到3及(負無窮,3(最後一個)]
-
如果是>3那麼我們需要將記錄定位到4及[4,正無窮)
也就是說<=3和>3定位的區間是相同的3-4
那麼我們將這裡的區間兩個值記為low-high
規律2:
仔細分析後我們發現另外一個規律
- (>) PAGE_CUR_G和(>=) PAGE_CUR_GE 都是取high
- (< )PAGE_CUR_L和(<=) PAGE_CUR_LE 都是取low
為什麼講這個東西,因為這兩個規律在innodb記錄定位中起到了關鍵作用,也直接影響到了innodb記錄查詢的二分演算法的實現方式。
二、matched_fields和matched_bytes
大家在原始碼中能看到matched_fields和matched_bytes兩個值,那麼他們代表什麼意思呢?
以int型別為例,因為在函式cmp_dtuple_rec_with_match_bytes是逐個欄位逐個位元組進行比較的,關鍵程式碼如下
while (cur_field < n_cmp) { rec_byte = *rec_b_ptr++; dtuple_byte = *dtuple_b_ptr++;}
比如int 2,int 3在innodb中內部表示為0X80000002和0X80000003,如果他們進行比較那麼最終此field的比較為不相等(-1),那麼matched_fields=0但是
- 0X 800000 02
-
0X 800000 03
我們能夠發現其中有3個位元組是相同的及0X80 0X00 0X00 所以matched_bytes=3
簡單的說matched_fields為相同field數量,如果field不相同則會返回相同的位元組數。
當然cmp_dtuple_rec_with_match_bytes對不同資料型別的比較方式也不相同如下:
switch (type->mtype) { case DATA_FIXBINARY: case DATA_BINARY: case DATA_INT: case DATA_SYS_CHILD: case DATA_SYS: break; case DATA_BLOB: if (type->prtype & DATA_BINARY_TYPE) { break; } default: ret = cmp_data(type->mtype, type->prtype, dtuple_b_ptr, dtuple_f_len, rec_b_ptr, rec_f_len); if (!ret) { goto next_field; } cur_bytes = 0; goto order_resolved; }
具體可以參考一下原始碼,這裡不再過多解釋
三、塊內二分查詢方法再析
為什麼叫做再析,因為如運維內參已經對本函式進行了分析,這裡主要分析查詢模式對二分法實現的影響,並且用圖進行說明你會有新的感悟!當然如果你對什麼slot還不清楚請自行參考運維內參
簡單的說page_cur_search_with_match_bytes會呼叫cmp_dtuple_rec_with_match_bytes函式進行元組和記錄之間的比較,而塊內部比較方法就是先對所有的slot進行二分查詢確定到某個slot以快速縮小範圍,然後在對slot內部使用類似二分查詢的方法等到記錄,我們主要來分析一下slot內部的類二分法,因為它完全是我們查詢模式中兩個規律的完美體現,如下簡化的程式碼片段以及我寫的註釋:
/* Perform linear search until the upper and lower records come to distance 1 of each other. */ while (page_rec_get_next_const(low_rec) != up_rec) { //如果low_rec和up_rec相差1則結束迴圈,否則繼續 mid_rec = page_rec_get_next_const(low_rec);//這裡並沒有除以2作為mid_rec而是簡單的取下一行,因為rec是單連結串列這樣顯然很容易完成 ut_pair_min(&cur_matched_fields, &cur_matched_bytes, low_matched_fields, low_matched_bytes, up_matched_fields, up_matched_bytes); offsets = rec_get_offsets( mid_rec, index, offsets_, dtuple_get_n_fields_cmp(tuple), &heap);//獲得記錄的各個欄位的偏移陣列 cmp = cmp_dtuple_rec_with_match_bytes( tuple, mid_rec, index, offsets, &cur_matched_fields, &cur_matched_bytes);//進行比較 0為相等 1 元組大於記錄 -1記錄大於元組,並且傳出field和bytes if (cmp > 0) { //如果元組大於mid_rec記錄 low_rec_match://當然簡單的將mid_rec指標賦予給low_rec即可 low_rec = mid_rec; low_matched_fields = cur_matched_fields; low_matched_bytes = cur_matched_bytes; } else if (cmp) { //如果元組小於mid_rec記錄 up_rec_match://當然簡單的將mid_rec指標賦予給up_rec即可,這一步可以跳過很多記錄 up_rec = mid_rec; up_matched_fields = cur_matched_fields; up_matched_bytes = cur_matched_bytes; } //下面是相等情況的判斷非常關鍵符合我們規律1演算法 //如果元組等於mid_rec else if (mode == PAGE_CUR_G || mode == PAGE_CUR_LE //如果是>(PAGE_CUR_G)和<=(PAGE_CUR_LE) ) { goto low_rec_match; //執行low_rec_match } else //如果是>=(PAGE_CUR_GE)和<(PAGE_CUR_L) { goto up_rec_match;//執行up_rec_match } } //下面體現我們的規律2演算法 //如果是> PAGE_CUR_G和>= PAGE_CUR_GE 都是取high //如果是< PAGE_CUR_L和<= PAGE_CUR_LE 都是取low //因為是enum型別直接比較 if (mode <= PAGE_CUR_GE) { page_cur_position(up_rec, block, cursor); } else { page_cur_position(low_rec, block, cursor); } *iup_matched_fields = up_matched_fields; *iup_matched_bytes = up_matched_bytes; *ilow_matched_fields = low_matched_fields; *ilow_matched_bytes = low_matched_bytes;
注意一個slot的own記錄為最多8條如下定義:
/* The maximum and minimum number of records owned by a directory slot. The number may drop below the minimum in the first and the last slot in the directory. */ #define PAGE_DIR_SLOT_MAX_N_OWNED 8 #define PAGE_DIR_SLOT_MIN_N_OWNED 4
如果大於了8則進行分裂
if (n_owned == PAGE_DIR_SLOT_MAX_N_OWNED) { page_dir_split_slot( page, NULL, page_dir_find_owner_slot(owner_rec)); }
下面我們畫一個slot內部定位的圖,我們以如下有序資料為例,假設每一個數字代表一個記錄(rec)
1 2 2 2 3 3 4 4
我們可以看到有大量重複的記錄,但是本演算法也可以進行精確的定位,我們約定:
- 紅色箭頭為最後定位到的值
- 黃色箭頭為mid rec
- 黑色箭頭分別表示low rec\high rec
如果是我們要定位到>2,那麼我們明顯要定位到2-3同時取high值3,我們用原始碼中的程式碼推匯出整個過程如下:
-
mid為2顯然已經等於了元組的中的2,如圖
-
但是查詢模式為PAGE_CUR_G 做low_rec_match操作、並且將mid取向下一條記錄後如圖
-
mid為2顯然已經等於了元組的中的2,但是查詢模式為PAGE_CUR_G做low_rec_match後、並且將mid取向下一條記錄如圖
-
mid為2顯然已經等於了元組的中的2,但是查詢模式為PAGE_CUR_G做low_rec_match後、並且將mid取向下一條記錄如圖
-
mid為3顯然已經大於了元組中的2,做up_rec_match後我們發現記錄定位成功,為low 2-high 3。page_rec_get_next_const(low_rec) == up_rec 迴圈退出如圖
-
因為我們的查詢模式是PAGE_CUR_G所以我們執行page_cur_position(up_rec, block, cursor);取high值如圖
如果我們要定位到<3,那麼我們明顯要定位到2-3.並且取low值2。我們用原始碼中的程式碼推匯出整個過程如下
-
mid為2顯然小於元組的中的3,如圖
-
做low_rec_match操作、並且將mid取向下一條記錄後如圖
-
mid為2顯然小於元組的中的3,做low_rec_match操作、並且將mid取向下一條記錄後如圖
-
mid為2顯然小於元組的中的3,做low_rec_match操作、並且將mid取向下一條記錄後如圖
-
mid為3顯然等於元組的中的3,但是查詢模式為PAGE_CUR_L做up_rec_match後、我們發現記錄定位成功為low 2-high 3.page_rec_get_next_const(low_rec) == up_rec 迴圈退出如圖
-
因為我們的查詢模式是PAGE_CUR_L所以我們執行page_cur_position(low_rec, block, cursor);取low值如圖
四、總結
我們slot內部的記錄並不多最多為8條,二分演算法slot內部並沒有使用二分而是使用了取下一個記錄的值的指標,非常容易實現因為記錄中本來就包含了下一條記錄的偏移量,並且透過訪問模式兩個規律將重複值過濾掉,最終找到邊界。總之分析之後發現是一種精確高效的演算法。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2144744/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 關於 mysql 中的 rand () 查詢問題MySql
- MYSQL INNODB中hash查詢表的實現MySql
- innodb查詢鎖
- 關於mysql 子查詢中 使用 limitMySqlMIT
- Dreamweaver關於媒體查詢命令的使用方法
- MySQL 中 MyISAM 中的查詢為什麼比 InnoDB 快?MySql
- 關於查詢列表準確定位元素的問題,通常如何解決
- sqlalchemy在python中的使用(關於查詢)二SQLPython
- Java中關於二分查詢的問題Java
- git stash關於程式碼中bug的查詢使用Git
- Python—Django:關於在Django框架中對資料庫的查詢函式,查詢集和關聯查詢PythonDjango框架資料庫函式
- 關於SQL Server資料查詢基本方法的總結SQLServer
- 關於EJB查詢返回值的解決方法 (轉)
- 關於oracle的空間查詢Oracle
- 關於UPDATE中關聯查詢的執行時間考慮
- Oracle:優化方法總結(關於連表查詢)Oracle優化
- MySQL中MyISAM為什麼比InnoDB查詢快MySql
- mysql中的多表關聯查詢MySql
- thinkphp中的多表關聯查詢PHP
- 關於字串匹配查詢的總結字串匹配
- 關於Hibernate的查詢問題
- 關於批次分頁查詢
- 關於查詢塊query blockBloC
- MySQL 5.7 查詢InnoDB鎖表MySql
- Elasticsearch——定位不合法的查詢Elasticsearch
- CSS中關於定位及BFC中的易錯點CSS
- 關於MySQL8的WITH查詢學習MySql
- 關於dataguard需要查詢的資料字典
- 關於分頁查詢的優化思路優化
- 關於mysql查詢字符集不匹配問題的解決方法MySql
- python中模組和方法的查詢Python
- 關於index檔案呼叫查詢Index
- 閃回(關於閃回查詢)
- 關於頁面中彈窗的定位問題
- oracle中基於ROWNUM的查詢的返回Oracle
- 關於鎖的快速定位
- 常用的查詢find和定位locate的用法
- 關於MySQL 通用查詢日誌和慢查詢日誌分析MySql