InnoDB文件物理结构解析6 - FIL_PAGE_INDEX

本文讨论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。

你可能感兴趣的:(MySQL,mysql)