天天看點

innblock | InnoDB page觀察利器

筆者是知數堂早期學員,最初有寫這麼一個工具的想法也得到葉金榮老師的認可和鼓勵,這個想法也整整耗掉了好幾個晚上的休息時間,這裡再次感謝葉金榮老師對工具稽核,葉老師的經驗和學識是每一位學員寶貴的财富。

感謝《MySQL運維内參》主要作者周彥偉、王竹峰、強昌金對本工具的認可并授權引用部分内容,如果大家對本文的内容感到吃力可以自行參考該書,也可以和書中部分章節配合使用學習。

感謝我所在的易極付公司DBA團隊同僚戴正勇、楊海波、田興椿、鄒啟健,在我研究和編寫代碼的時候承擔了大部分的資料庫相關的工作,能在重慶遇到你們是我的榮幸,你們是最出色的。

百度雲盤位址:

http://pan.baidu.com/s/1qYnyVWo

InnoDB中索引塊的内部組織一直是大家比較感興趣并且樂于研究的東西,我們從很多書籍和文章都不惜筆墨進行大量的描述比如<>中就能感受到作者用了大量篇幅描述什麼是slot、什麼是heap、記錄的邏輯和實體順序是怎麼樣的。運維内參>

但是我們卻很難直覺的看到,因為資料檔案是二進制檔案。雖然我們可以通過例如LINUX的hexdump等類似指令進行檢視,但是大量的16進制資訊很難直覺的提取出各種有用的資訊,相信不少人和筆者一樣都是通過肉眼進行檢視,但是這顯然是一種吃力又不讨好的方法。

在Oracle中我們可以通過dump block的方法檢視block的資訊,那麼InnoDB是否也可以這樣呢?

本着這種讓大家更加直覺的觀察到底層索引塊的資訊的宗旨,筆者直接借用源碼中的各種宏定義,使用C++和STL list容器實作了這樣一個工具innblock。由于工作原因不能全身心投入代碼編寫,代碼有些混亂。是以如果有bug還請大家見諒以及提出,筆者會盡快進行更新,感謝。

index page(索引頁、索引塊),InnoDB表是基于聚集索引的索引組織表,整個表其實不是聚集索引,就是普通索引。是以InnoDB表空間檔案中,資料頁其實也是索引頁,是以下面我們統稱為索引頁,英文用page no表示;

本工具有2個功能。

第一個scan功能用于查找ibd檔案中所有的索引頁。

第二個analyze功能用于掃描資料塊裡的row data。

先看下 help 輸出

innblock | InnoDB page觀察利器

scan功能

analyze功能

可以執行 <code>innblock help</code> 獲得更詳細的使用幫助資訊。

不支援REDUNDANT行格式的資料檔案;

隻支援LINUX x64平台;

本工具直接讀取實體檔案,部分dirty page可能延時刷盤而未能被讀取到,可以讓InnoDB及時刷盤再重新讀取;

最好在MySQL 5.6/5.7版本下測試;

隻能解析索引頁,不支援inode page、undo log等類型的page;

scan功能會包含delete後的索引塊和drop了的索引塊.

不能讀取詳細的row data;

建議采用獨立表空間模式,更便于觀察;

建議僅在測試環境下學習和研究使用。

首先,建立測試表,填充資料

innblock | InnoDB page觀察利器

我們發現有3個索引,索引ID(INDEX_ID)分别是 248、249、250,檢視資料字典确認

innblock | InnoDB page觀察利器

我們選取 pageno=3 那個索引頁進行掃描,可見下面資訊

innblock | InnoDB page觀察利器

我在工具的help文檔中也有詳細的解釋,這裡單獨對analyze功能解析資料塊的輸出詳解一番,并且我也會給出這些值來自源碼的哪個宏定義。這部分知識點在&lt;&gt;中也有詳細說明。運維内參&gt;

[block_no]:page offset no inside space,begin is 0(取自 FIL_PAGE_OFFSET) 索引頁碼(index page no),該頁相對于表空間的偏移量,從0開始計數。如果page no = 3,則實際上是第4個index page。

[space_id]:this contains the space id of the page(FIL_PAGE_SPACE_ID) 本索引頁所屬的表空間ID,可以在 INNODB_SYS_TABLES、INNODB_SYS_TABLESPACES、INNODB_SYS_DATAFILES 等系統視圖中檢視。

[index_id]:index id where the page belongs.This field should not be written to after page creation. (PAGE_INDEX_ID) 本索引頁所屬的索引ID,可以在 INNODB_SYS_INDEXES 系統視圖中檢視。

[slot_nums]:number of slots in page directory(PAGE_N_DIR_SLOTS) 本索引頁中所包含的slot(槽)的數量。

[heaps_rows]:number of records in the heap include delete rows after purge and INFIMUM/SUPREMUM(取自PAGE_N_HEAP) 本索引頁中的全部記錄數量,這其中包含了已經deleted且已被purged的記錄(這種記錄會被放到索引頁的garbage隊列中),以及兩個僞記錄INFIMUM/SUPREMUM。

[n_rows]:number of records not include delete rows after pruge and INFIMUM/SUPREMUM(PAGE_N_RECS) 本索引頁中的記錄數,不含deleted且已被purged的記錄,以及兩個僞記錄INFIMUM、SUPREMUM。

[heap_top]:pointer offset to record heap top (PAGE_HEAP_TOP) 指向本索引頁已配置設定的最大實體存儲空間的偏移量。

[del_bytes]:number of bytes in deleted records after purge(PAGE_GARBAGE) 本索引頁中所有deleted了的且已被purged的記錄的總大小。

[last_ins_offset]:pointer to the last inserted record, or NULL if this info has been reset by a delete(PAGE_LAST_INSERT) 指向本索引頁最後插入記錄的位置偏移量,如果最後操作是delete,則這個偏移量為空。通過判斷索引頁内資料最後插入的方向,用于索引分裂判斷。

[page_dir]:last insert direction: PAGE_LEFT, ...(PAGE_DIRECTION) 本索引頁中資料最後插入的方向,同樣用于索引分裂判斷。

[page_n_dir]:number of consecutive inserts to the same direction(PAGE_N_DIRECTION) 向同一個方向插入資料的行數,同樣用于索引分裂中進行判斷

[leaf_inode_space leaf_inode_pag_no leaf_inode_offset]:leaf segment postion and in inode block offset,only root block(PAGE_BTR_SEG_LEAF開始 10位元組)

[no_leaf_inode_space no_leaf_inode_pag_no no_leaf_inode_offset]:no_leaf segment postion and in inode block offset,only root block(取自PAGE_BTR_SEG_TOP 開始 10位元組) 這6個值隻在root節點會有資訊,分别表示了葉子段和非葉子段的inode的位置和在inode塊中的偏移量,其他塊都為0。

[last_modify_lsn]:lsn of the end of the newest modification log record to the page(FIL_PAGE_LSN) 本塊最後一次修改的LSN。

[page_type]:for this tool only B+_TREE(FIL_PAGE_TYPE) 對于本工具而言始終為B+ TREE,因為不支援其它page type。

[level]:level of the node in an index tree; the leaf level is the level 0(PAGE_LEVEL) 本索引頁所處的B+ TREE的層級。注意,葉子結點的PAGE LEVEL為0。

Total used rows:5 used rows list(logic): not delete purge rows and not delete logic sequence list(next offset list). 這個連結清單是邏輯有序連結清單,也是我們平時所說的塊内資料有序的展示。它的順序當然按照主鍵或者ROWID進行排列,因為是通過實體偏移量連結清單實作的,實際上就是邏輯上有序。我在實作的時候實際上是取了INFIMUM的偏移量開始進行掃描直到最後,但是注意被deleted且已經被purged的記錄不在其中。

Total used rows:5 used rows list(phy): not delete purge rows and not delete physics sequence list(sort by heap no). 這個連結清單是實體上的順序,實際上就是heap no的順序,我在實作的時候實際上就是将上面的邏輯連結清單按照heap no進行排序完成的,是以塊内部是邏輯有序實體無序的,同樣注意被deleted且已被purged的記錄不在其中。

Total del rows:1 del rows list(logic): purge delete logic sequence list(next offset list). 這個連結清單是邏輯上的,也就是被deleted且被purged後的記錄都存在于這個連結清單中,通過讀取塊的PAGE_FREE擷取連結清單資訊。

Total slot:2 slot list: slot physics sequence list. 這是slot(槽的)資訊,通過掃描塊尾部8位元組以前資訊進行分析得到,我們可以發現在slot中存儲的是記錄的偏移量。

在這裡連結清單中包含一些資訊,這裡就用help中的解析給出了。

[record offset]:real offset in block of this record.

[heapno]:physics heapno of this record.

[n_owned]:if this record is slot record n_owned is how many this slot include,other is 0.

[delflag]:this record is delete will Y,if not purge in list 1,if purge in list 3.

[rectype]: [REC_STATUS_ORDINARY=0(B+ leaf record) [REC_STATUS_NODE_PTR=1(not B+ leaf record)] [REC_STATUS_INFIMUM=2] [REC_STATUS_SUPREMUM=3]

[slot offset]:where(offset) this slot point,this is a record offset.no purge delete record.

[n_owned]:how many this slot include recorods.no purge delete record.

本節全部使用測試表如下:

初始化測試資料:

發起事務,先執行delete,暫不commit

分析結果:

innblock | InnoDB page觀察利器

我們看到其中有一條記錄是

其 delflag = Y,offset = 127,這條記錄隻是delete,但還沒 commit,也還沒被 purged,是以不會出現在 del rows list連結清單中。

同時注意到幾個資訊:

del_bytes:0

n_rows:4

heaps_rows:6

三個資訊結合起來看,表示還沒有真正被清除的資料。

接着上面的事務,繼續執行commit

innblock | InnoDB page觀察利器

我們看到,執行commit,這條偏移量為127的記錄被purged後入了del rows list連結清單

其delflag = Y,同時我們觀察到

del_bytes:31 //上一次看到的值是 0

n_rows:3 //上一次看到的值是 4

heaps_rows:6 //和上一次的值一樣,因為這裡計算的是實體記錄數

可見,commit且被purged的資料才是真正的删除(清除)。

上面删除的記錄的heapno為2,接着插入新記錄

顯然它的長度大于删除記錄的長度。

innblock | InnoDB page觀察利器

我們看到有一條新記錄

這條記錄的heapno = 6,而删除的舊記錄 heapno=2,這表明它沒有重用del rows list中的空間,因為删除記錄的空間根本放不下這條新記錄,是以隻能重新配置設定。同時我們注意到 heap_top = 279 ,這裡也發生了變化,展現了實際為這行資料配置設定了新的heapno。

在上面的基礎上,我們插入新記錄

innblock | InnoDB page觀察利器

我們這次新寫入的資料長度和删除的資料長度一緻,我們發現heapno重用了del rows list中的記錄沒有了,而在資料邏輯順序中多了一條

我們發現heapno=2的記錄 delflag 不再是 Y了,同時 heap_top = 279 也沒有變化,del_bytes:31 變成了 del_bytes:0,都充分說明了這塊空間得到重用。

清空資料表後執行測試

在這裡,我們先删除 [id1=4] 記錄,後删除 [id1=3] 記錄。 由于del list是頭插法,是以後删除的 [id1=3] 的記錄會放在del list連結清單的最頭部,也就是 [del list header] =&gt; [id1=3] =&gt; [id1=4]。雖然 [id=4] 的記錄空間足以容下新記錄 (5,'gaopeng’,5),但并沒被重用。因為InnoDB隻檢測第一個 del list 中的第一個空位 [id1=3],顯然這個記錄空間不足以容下新記錄 (5,’gaopeng',5),是以還是新開辟了heap。

innblock | InnoDB page觀察利器

我們看到 del list 中共有2條記錄(沒被重用),卻新增加了 heapno = 6 的記錄。

從重組函數 btr_page_reorganize_low 來看,PAGE_GARBAGE确實包含了碎片空間。

innblock | InnoDB page觀察利器

注意這裡 del_bytes:35 就是删除這條記錄的空間的使用量。接下來執行SQL

再次分析結果:

innblock | InnoDB page觀察利器

注意到 del_bytes:4,這個剛好就是 'gaopeng' 7位元組減去 'gao' 3位元組剩下的4位元組,我們也看到了 [heapno=5] 這個記錄被重用了(del list為空,heaono=5的記錄 delflag 不為 Y)。

總之本工具可以按照你的想法進行各種測試和觀察。

實際上本工具我并沒有顯示的配置設定記憶體,記憶體配置設定基本使用了STL LIST容器檢測結果如下:

本工具基本采集了InnoDB索引頁全部固定資訊,希望能夠幫助大家更友善獲得各種資訊,效率顯然高于肉眼看二進制檔案,這是作者在分析InnoDB遇到的困境,也是寫這個小工具的出發點。 最後再次感謝葉金榮對工具稽核&amp;建議以及《MySQL運維内參》三位作者周彥偉、王竹峰、強昌金對本工具的認可,這也是我個人最大的榮耀運維内參&gt;

運維内參&gt;

原文釋出時間為:2017-10-01

本文作者:高鵬(重慶八怪)

本文來自雲栖社群合作夥伴“老葉茶館”,了解相關資訊可以關注“老葉茶館”微信公衆号