本文討論Secondary Key Page的解析,也就是表非主鍵索引的記錄存儲。與Clustered Key Page有相同的基本記錄結構,也細分為Leaf Page和Non-Leaf Page,我們先看結構:
### Contents (Secondary Key - Leaf Page) ###
------------------------+------------------------+
Secondary Key Fields (k)| Cluster Key Fields (j) |
------------------------+------------------------+### Contents (Secondary Key - Non-Leaf Page) ###
-----------------------------------------+----------------------------------------+-----------------------+
Secondary Key Min. Key on Child Page (k) | Cluster Key Min. Key on Child Page (j) | Child Page Number (4) |
-----------------------------------------+----------------------------------------+-----------------------+
先看Leaf Page, 記錄的基本結構都是一樣的,有Extra Bytes, null-bitmap, variable-field-lengths部分,但是內容不同。InnoDB中,非主鍵索引(組合索引)字段是允許包含空值的。null-bitmap用于標記Secondary Key Fields中的空值。variable-field-lengths包含Secondary和Cluster Key的可變長字段的記錄存儲的長度。
記錄內容中,最前面部分存放索引的字段,后面接著索引字段值對應的主鍵值,記錄內容不包含DB_TRX_ID和DB_ROLL_PTR偽字段。以sakila.film表的idx_title(index_id = 597)索引為例,根據《InnoDB文件物理結構解析2》的IdxPage1案例的輸出,idx_title索引的相關頁為:
PAGE PAGE_TYPE LEVEL INDEX_ID PAGE_PREV PAGE_NEXT
----- --------------- ----- -------- ----------- -----------5 FIL_PAGE_INDEX 1 597 4294967295 4294967295
16 FIL_PAGE_INDEX 0 597 4294967295 17
17 FIL_PAGE_INDEX 0 597 16 4294967295
所以,page(5)是Secondary Key Non-Leaf Page,page(16)和page(17)為Secondary Key Leaf Page,我們寫個案例分析page(16)的內容:
public class IdxPage6 {public static void main(String[] args) throws Exception {String fileName = "D:\\Data\\mysql\\8.0.18\\data\\sakila\\film.ibd";try (IbdFileParser parser = new IbdFileParser(fileName)) {IndexPage page = (IndexPage) parser.getPage(16);long indexId = page.getIndexHeader().getIndexId().longValueExact();TableMeta tableMeta = IdxPage3.getFilmTableMeta(); 定義索引的元數據(索引包含哪些列)tableMeta.setSecondaryKey(indexId, 1, "title");SecondaryKeyLeafPage leafPage = new SecondaryKeyLeafPage(page.getPageRaw(), page.getPageSize());List<SecondaryKeyLeafRecord> records = leafPage.getUserRecords(tableMeta);StringBuilder buff = new StringBuilder();buff.append("Secondary Key Fields Cluster Key Fields \n").append("-------------------------------- ------------------------\n");for (SecondaryKeyLeafRecord record : records) {List<RecordField> skFields = record.getSecondaryKeyFields();List<RecordField> ckFields = record.getClusterKeyFields();StringBuilder skValue = new StringBuilder();StringBuilder ckValue = new StringBuilder();for(RecordField field: skFields) {skValue.append(field.getName()).append(" = ").append(field.getContent()).append(" ");}buff.append(String.format("%-32s ", skValue));for(RecordField field: ckFields) {ckValue.append(field.getName()).append(" = ").append(field.getContent()).append(" ");}buff.append(String.format("%-24s\n", ckValue));}System.out.println(buff);}}
}
/*
程序輸出:
Secondary Key Fields Cluster Key Fields
-------------------------------- ------------------------
title = ACADEMY DINOSAUR film_id = 1
title = ACE GOLDFINGER film_id = 2
title = ADAPTATION HOLES film_id = 3
title = AFFAIR PREJUDICE film_id = 4
title = AFRICAN EGG film_id = 5
title = AGENT TRUMAN film_id = 6
title = AIRPLANE SIERRA film_id = 7
title = AIRPORT POLLOCK film_id = 8
title = ALABAMA DEVIL film_id = 9
title = ALADDIN CALENDAR film_id = 10
title = ALAMO VIDEOTAPE film_id = 11
...
*/
由輸出可以看到,InnoDB索引,記錄的是索引值對應的主鍵值,而不是行記錄的地址,這與Oracle數據庫的索引是不同的,或許與InnoDB是索引組織表有關,行記錄存儲在主鍵中。找條輸出驗證一下解析結果:
root@localhost [sakila]> select title from film where film_id=10;
+------------------+
| title |
+------------------+
| ALADDIN CALENDAR | // film_id =10 <--> title = ALADDIN CALENDAR與預期的一樣;
+------------------+
1 row in set (0.00 sec)
我們接下來通過案例解析page(5), Non-Leaf頁:
public class IdxPage7 {public static void main(String[] args) throws Exception {String fileName = "D:\\Data\\mysql\\8.0.18\\data\\sakila\\film.ibd";try (IbdFileParser parser = new IbdFileParser(fileName)) {IndexPage page = (IndexPage) parser.getPage(5);long indexId = page.getIndexHeader().getIndexId().longValueExact();TableMeta tableMeta = IdxPage3.getFilmTableMeta(); 定義索引的元數據(索引包含哪些列)tableMeta.setSecondaryKey(indexId, 1, "title");SecondaryKeyNonLeafPage rootPage = new SecondaryKeyNonLeafPage(page.getPageRaw(), page.getPageSize());List<SecondaryKeyNonLeafRecord> records = rootPage.getUserRecords(tableMeta);StringBuilder buff = new StringBuilder();buff.append("Secondary Key Min. Key on Child Page Cluster Key Min. Key on Child Page Child Page Number \n").append("------------------------------------ ----------------------------------- ------------------\n");for(SecondaryKeyNonLeafRecord record : records) {List<RecordField> minSkFields = record.getMinSecondaryKeyOnChild();List<RecordField> minCkFields = record.getMinClusterKeyOnChild();long childPageNo = record.getChildPageNumber();StringBuilder skValue = new StringBuilder();StringBuilder ckValue = new StringBuilder();for(RecordField field: minSkFields) {skValue.append(field.getName()).append(" = ").append(field.getContent()).append(" ");}buff.append(String.format("%-36s ", skValue));for(RecordField field: minCkFields) {ckValue.append(field.getName()).append(" = ").append(field.getContent()).append(" ");}buff.append(String.format("%-35s ", ckValue));buff.append(String.format("%-15d\n", childPageNo));}System.out.println(buff);}}
}
/*
程序輸出:
Secondary Key Min. Key on Child Page Cluster Key Min. Key on Child Page Child Page Number
------------------------------------ ----------------------------------- ------------------
title = ACADEMY DINOSAUR film_id = 1 16
title = GILMORE BOILED film_id = 358 17
*/
索引深度level>0時,根據非葉子節點內的值范圍找葉子節點,再根據所在葉節點中找到對應的主鍵值,然后根據主鍵"回表",找到對應行記錄。
到這里,FIL_PAGE_INDEX的解析介紹完成,下文將介紹一下8.0新引入的FIL_PAGE_SDI。