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)的内容:

java 复制代码
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是索引组织表有关,行记录存储在主键中。找条输出验证一下解析结果:

sql 复制代码
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页:

java 复制代码
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。

相关推荐
梦想的旅途21 小时前
企业微信外部群主动调用:RPA 接口与官方 API 的技术边界
网络·mysql·自动化·企业微信·rpa
ULIi096kr2 小时前
MySQL查看表创建时间、修改时间、最后更新时间(精准排查僵尸表)
数据库·mysql
折哥的程序人生 · 物流技术专研2 小时前
Tomcat 严重警告:JDBC 驱动未注销 + 工作线程泄漏 —— 原因、影响与彻底修复(生产级终极指南)
java·运维·数据库·mysql·oracle·tomcat
拄杖忙学轻声码3 小时前
mysql脚本查询数据,符合指定条件的排在数据列表最前面,实现方式
mysql
济*沧*海4 小时前
MySQL分库分表实战解析
mysql
天海华兮4 小时前
MySQL知识点 覆盖索引、MVCC、存储引擎、事务锁、性能优化等核心点
mysql·事务·日志·索引·mvcc·存储引擎·执行计划
Wait....4 小时前
MySQL底层知识总结
数据库·mysql
DolphinScheduler社区4 小时前
实战演示 | 基于 Apache DolphinScheduler 与 Apache SeaTunnel 实现 MySQL 到 Doris 离线定时增量同步
数据库·mysql·开源·apache·海豚调度·大数据工作流调度
承渊政道4 小时前
【MySQL数据库学习】MySQL基本查询(下)
数据库·学习·mysql·leetcode·bash·数据库开发·数据库系统
摇滚侠4 小时前
Spring 零基础入门到进阶 基于注解的声明式事务 65-70
数据库·mysql·spring