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

我们继续探讨FIL_PAGE_INDEX页的解析, 本章我们聚焦于解析FIL_PAGE_INDEX页存储的记录。接前文,INDEX_Header结构后面接着的是FSEG Header,FSEG Header用于InnoDB段管理的,跟记录读取无关,这里不做讨论,这部分mysql-file-parser也进行了解析,网上也有一些介绍,有兴趣可以自行研究。

FSEG_Header结构接下来的就是System_Records, System_Records包含Infimum和Supremum记录,我们看结构:

 94+========== Infimum Record =========+
   |               Info Flags (4 bits) |
   |  Number of Records Owned (4 bits) |
 95+-----------------------------------+
   |                   Order (13 bits) |
   |              Record Type (3 bits) |
 97+-----------------------------------+
   |      Next Record Offset (2 bytes) |
 99+-----------------------------------+
   |                 Content (8 bytes) | //固值为: "infimum "
107+========== Supremum Record ========+
   |               Info Flags (4 bits) |
   |  Number of Records Owned (4 bits) |
108+-----------------------------------+
   |                   Order (13 bits) |
   |              Record Type (3 bits) |
110+-----------------------------------+
   |       Next Record Offset(2 bytes) |
112+-----------------------------------+
   |                 Content (8 bytes) | //固值为: "supremum"
120+-----------------------------------+

* 前面的数字为页内偏移(bytes)

前文提到,InnoDB用户数据在页内的存储,物理上也是无序存放的,这和堆表类似,但是InnoDB会预留预留一些空间,来存储一个单向链表(singly-linked)结构,来让页内的数据按主键有序串连起来。这个单向链表的头就是Infimum Record,而尾就是Supremum Record。FIL_PAGE_INDEX的记录逻辑结构如下:

[infimum record  |  supremum record]
  \                              /
   \         [Record 1]         /
    \          |    \          /
     \         |  [record 2]  /
   [record 3]--/      \      /
	               [record ...] 

Extra Bytes可以看成是某一记录在页内存储的元数据。注意:根据前面概述中介绍,不同行格式,Extra Bytes的是不同的,我们只讨论COMPACT/DYNAMIC行格式的Extra Bytes结构,因为它们是相同的, 都是有5个字节, 分别代表如下:

--------------------------------------------------------------------------------+
                       REC_NEW_INFO_BITS(Info Flags)                            |
--------------------------------------------------------+-----------------------+-----------------+--------------------+-----------------+-----------------+
           1 bit       | 1 bit  |          1 bit        |        1 bit          |      4 bits     |     13 bits        |    3 bits       |    2 bytes      | 
-----------------------+--------+-----------------------+-----------------------+-----------------+--------------------+-----------------+-----------------+
REC_INFO_INSTANT_FLAG  | unused | REC_INFO_DELETED_FLAG | REC_INFO_MIN_REC_FLAG | REC_NEW_N_OWNED |   REC_NEW_HEAP_NO  | REC_NEW_STATUS  |   REC_NEXT      |
-----------------------+--------+-----------------------+-----------------------+-----------------+--------------------+-----------------+-----------------+

所有的FIL_PAGE_INDEX的遍历记录方式都是一致的,就是通过单链表,从infimum开始,通过Extra Bytes的next_offset找到下一条记录,直到supremum。infimum的Next Record Offset是一个指针,指向的是下一条记录的位置,这个下一条记录位置到底是什么位置?记录的开始?结尾?都不是,而是记录的Extra Bytes结构的结尾。

Infimum Record         User Records         Supremum record
+=Extra Bytes=+       +=Extra Bytes=+       +=Extra Bytes=+
|   ...       |       |    ...      |       |    ...      | 
+-------------+       +-------------+       +-------------+
|  REC_NEXT   |       |  REC_NEXT   |       |  REC_NEXT   | 
+=============+ ----> +=============+ ----> +=============+
|  Content    |       |  Content    |       |  Content    |
+=============+       +=============+       +=============+

我们通过一个案例来演示这一个过程:

public class IdxPage2 {
	// SUPREMUM_REC_NEXT记录的结束位置, 根据System_Records结构图, 固定为112;
	public static final int SUPREMUM_REC_NEXT_END_POS = 112;
	// INFIMUM_REC_NEXT记录的结束位置, 根据System_Records结构图, 固定为99;
	public static final int INFIMUM_REC_NEXT_END_POS = 99;
	public static void main(String[] args) throws IOException, Exception {
		String fileName = "D:\\Data\\mysql\\8.0.18\\data\\sakila\\film.ibd";
		try (IbdFileParser parser = new IbdFileParser(fileName)) {
			IndexPage page = (IndexPage) parser.getPage(4);
			byte[] pageRaw = page.getPageRaw(); // 获取page(4)的页的数据(字节)
			SystemRecords systemRecords = page.getSystemRecords();
			
			RecordExtra infimumExtra = systemRecords.getInfimumExtra();   
			RecordExtra supremumExtra = systemRecords.getSupremumExtra();
			StringBuilder buff = new StringBuilder();
			buff.append("INSTANT_FLAG DELETED_FLAG MIN_REC_FLAG N_OWNED HEAP_NO          RECORD_TYPE REC_NEXT(OffSet|Pos)\n")
				.append("------------ ------------ ------------ ------- ------- -------------------- --------------------\n");
			// Extra的next是一个有符号的整数(Int16), 如果你插入的数据是无序(记录的主键无序)的,偏移有可能是个负数。
			// 这个相对偏移, 也就是INFIMUM_REC_NEXT结束的位置 + INFIMUM_REC_NEXT_END_POS
			int infimumRecOffset = infimumExtra.getNextRecordOffset();
			int infimumRecNextPos = infimumRecOffset + INFIMUM_REC_NEXT_END_POS;
			buff.append(dumpRecordExtra(infimumExtra));
			int currentPos = infimumRecNextPos;
			while (currentPos > SUPREMUM_REC_NEXT_END_POS) {
				int extraEndPos = currentPos;
				int extraStartPos = currentPos - IndexPage.REC_N_NEW_EXTRA_BYTES;//  5 bytes for DYNAMIC
				byte[] extraRaw = Arrays.copyOfRange(pageRaw, extraStartPos, extraEndPos);
				RecordExtra extra = new RecordExtra(extraRaw);
				int nextOffset = extra.getNextRecordOffset();
				int nextPos = nextOffset + extraEndPos;
				extra.setNextRecordPos(nextPos);
				buff.append(dumpRecordExtra(extra));
				if (nextPos == SUPREMUM_REC_NEXT_END_POS) {
					break;
				}
				currentPos = nextPos;
			}
			buff.append(dumpRecordExtra(supremumExtra));
			System.out.println(buff);
		}
	}
	public static String dumpRecordExtra(RecordExtra extra) {
		StringBuilder buff = new StringBuilder()
				.append(String.format("%12s ", extra.getInstantFlag()))
				.append(String.format("%12s ", extra.getDeletedFlag()))
				.append(String.format("%12s ", extra.getMinRecFlag()))
				.append(String.format("%7d ", extra.getRecordsOwned()))
				.append(String.format("%7d ", extra.getRecordHeapNo()))
				.append(String.format("%20s ", RecordExtra.getRecordType( extra.getRecordStatus())))
				.append(String.format("%20s", extra.getNextRecordOffset() + " | " + extra.getNextRecordPos()))
				.append("\n");
		return buff.toString();
	}
}
 程序输出:
INSTANT_FLAG DELETED_FLAG MIN_REC_FLAG N_OWNED HEAP_NO          RECORD_TYPE REC_NEXT(OffSet|Pos)
------------ ------------ ------------ ------- ------- -------------------- --------------------
       false        false        false       1       0   REC_STATUS_INFIMUM             27 | 126
       false        false         true       0       2  REC_STATUS_NODE_PTR             12 | 138
       false        false        false       0       3  REC_STATUS_NODE_PTR             12 | 150
       false        false        false       0       4  REC_STATUS_NODE_PTR             12 | 162
       false        false        false       4       5  REC_STATUS_NODE_PTR             12 | 174
       false        false        false       0       6  REC_STATUS_NODE_PTR             12 | 186
       false        false        false       0       7  REC_STATUS_NODE_PTR             12 | 198
       false        false        false       0       8  REC_STATUS_NODE_PTR             12 | 210
       false        false        false       0       9  REC_STATUS_NODE_PTR             12 | 222
       false        false        false       0      10  REC_STATUS_NODE_PTR             12 | 234
       false        false        false       0      11  REC_STATUS_NODE_PTR             12 | 246
       false        false        false       0      12  REC_STATUS_NODE_PTR           -134 | 112
       false        false        false       0       1  REC_STATUS_SUPREMUM              0 | 112

其中记录类型:
REC_STATUS_ORDINARY = 0 // 叶节点记录
REC_STATUS_NODE_PTR = 1 // 非叶节点记录
REC_STATUS_INFIMUM = 2 // infimum记录
REC_STATUS_SUPREMUM = 3 // supremum记录

因为案例的page(4)的page_level=1,也就是说存放的是非叶子节点,所以记录类型都是REC_STATUS_NODE_PTR,我们可以看到最后一条记录指向的位置(next_pos)为112,通过System_Records的结构图,可知这是supermum记录的"Next Record Offset(2 bytes)"位置,说明记录到此遍历完成。

我们可以看到Extra Bytes中有一个记录删除位(DELETED_FLAG),没错,和很多系统类似,InnoDB删除记录时通常只是置删除标志,而不是清空内容,后面也会对这个删除标志位做进一步得讨论。

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