[INNODB] ibd文件之通过B+树索引和页目录查找行数据

### 准备工作

首先,为了说明方便,本文使用了 [innoisp](https://github.com/sryanyuan/innoisp) 来解析ibd文件来获取各个page的信息。

ibd文件为innodb的存储文件,存储着各种数据,比如行数据、undolog、insert buffer等。

在任意情况下,会存在系统ibd文件,所有的库表数据都会在其中。用户也可以选择将表给分离开来,使用一个表一个ibd的方式进行存储。

假设读者已经熟悉ibd的基础知识,包括但不限于page的划分,以及各种page的用途,本文仅仅记录怎样通过解析ibd文件来寻找到某一行数据。

首先,我们需要确定主键类型,由于ibd文件没有表结构信息,所以我们需要知道主键类型来确定主键存储所需要的字节数。同时我们需要确定我们通过的是主键还是通过其余的索引来查找记录,在这里我们仅仅讨论使用primary key来查找记录的情形。

上述准备好后,我们就可以通过解析ibd文件来查找所需要的行数据了。假设我们要查找的key为2,下面来分步骤来解析:

### 确定根索引页 (Root index page)

根索引页用于定位到B+ tree的根节点来进行快速查找,为了得到root index page的页码,我们需要通过解析file segment inode页。

file segment inode实际上是一个将ibd文件再次分割成逻辑文件的管理结构。在一个table的ibd文件中,假设该表只有一个主键,则会有2个inode,分别是索引的inode和数据的inode。inode通过引用extend来划分extend。

所以我们只需要读取inode序号为1的所有的extend和fragment array信息即可。

我们使用innoisp来读取inode信息,可以得到如下的输出:

                            ==========PAGE 2 OFFSET 0x8000==========
    page list
    0xFFFFFFFF:0x0000 0xFFFFFFFF:0x0000
    
    file segment id     used(nf)  free list                                          not_full list                                      full list                                          fragment array
    0x00000032:1        0         len<0> 0xFFFFFFFF:0x0000 0xFFFFFFFF:0x0000         len<0> 0xFFFFFFFF:0x0000 0xFFFFFFFF:0x0000         len<0> 0xFFFFFFFF:0x0000 0xFFFFFFFF:0x0000         3 36 37 (page allocate)
    0x000000F2:2        36        len<0> 0xFFFFFFFF:0x0000 0xFFFFFFFF:0x0000         len<1> 0x00000000:0x047E 0x00000000:0x047E         len<24> 0x00000000:0x00BE 0x00000000:0x0456        4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 (extend allocate)

我们可以看到,inode 1的fragment array中第一页是3,那么root index page的序号也就是3。

### 查找B+树非叶子页

我们在确定了根索引页后,这里其实有两种情况:

1. Root index page既是leaf page又是internal page。这种情况下,直接搜索directory slot就可以得到行数据。这种情况在数据量很少的情况下会出现。
2. Root index page只是non-leaf page。这种情况比较多见,数据量大了后,会分裂出leaf和non-leaf page,leaf存储行数据,non leaf作为B+ tree的索引节点。

我们本文主要讨论第二种情况,也就是root index page是non leaf page的情况。

我们首先使用innoisp来查找root index page的directory slot(槽)信息:

                            ==========PAGE 3 OFFSET 0xC000 LEVEL 2==========
    slot    offset      type        owned   key     page ptr
    0       0x0063      infimum     1       N/A     0
    slot reference: [infimum own 1 0x0063 ->P0]
    1       0x0070      supremum    3       N/A     N/A
    slot reference: [0x007D PK1 ->P36]->[0x008A PK180751 ->P37]->[supremum own 3 0x0070]

从中我们可以得知,该页有2个页目录,2个系统行,其中supremum拥有3个行记录(除去自己还有2个),分别是key为1和180751的记录。

查找directory slot的方法,追求速度得用二分法,因为directory slot是一个有序数组,可以通过二分来快速找到对应的区间。在这里我们只有2个槽,所以直接取supremum槽即可。注意一点,槽的查找应当是左开右闭的,也就是当前的slot是闭区间,前一个slot是开区间,假设key落在这个范围内,则当前slot命中。

命中了slot后,也就是supremum槽,当前槽拥有了3条记录,其余两条记录的key分别为1和180751,这样就产生了两个区间:

1. [1, 180751)
2. [180751, 正无穷)

我们依次将key与范围进行比对,假设我们当前要查找的key为2,则落在区间1内,我们取出这条recorder,该recorder应当是一个指向下一个page的记录,下一页记录的页码为36,于是我们解析36页的directory slot:

                            ==========PAGE 36 OFFSET 0x90000 LEVEL 1==========
    slot    offset      type        owned   key     page ptr
    0       0x0063      infimum     1       N/A     0
    slot reference: [infimum own 1 0x0063 ->P0]
    1       0x00A4      node ptr    4       753     7
    slot reference: [0x007D PK1 ->P4]->[0x008A PK151 ->P5]->[0x0097 PK452 ->P6]->[normal own 4 0x00A4 PK753 ->P7]
    2       0x00D8      node ptr    4       1957    11
    slot reference: [0x00B1 PK1054 ->P8]->[0x00BE PK1355 ->P9]->[0x00CB PK1656 ->P10]->[normal own 4 0x00D8 PK1957 ->P11]
    3       0x010C      node ptr    4       3161    15
    slot reference: [0x00E5 PK2258 ->P12]->[0x00F2 PK2559 ->P13]->[0x00FF PK2860 ->P14]->[normal own 4 0x010C PK3161 ->P15]
    4       0x0140      node ptr    4       4365    19
    slot reference: [0x0119 PK3462 ->P16]->[0x0126 PK3763 ->P17]->[0x0133 PK4064 ->P18]->[normal own 4 0x0140 PK4365 ->P19]
    5       0x0174      node ptr    4       5569    23
    slot reference: [0x014D PK4666 ->P20]->[0x015A PK4967 ->P21]->[0x0167 PK5268 ->P22]->[normal own 4 0x0174 PK5569 ->P23]
    6       0x01A8      node ptr    4       6773    27
    slot reference: [0x0181 PK5870 ->P24]->[0x018E PK6171 ->P25]->[0x019B PK6472 ->P26]->[normal own 4 0x01A8 PK6773 ->P27]
    7       0x01DC      node ptr    4       7977    31
    slot reference: [0x01B5 PK7074 ->P28]->[0x01C2 PK7375 ->P29]->[0x01CF PK7676 ->P30]->[normal own 4 0x01DC PK7977 ->P31]
    8       0x0210      node ptr    4       9181    35
    slot reference: [0x01E9 PK8278 ->P32]->[0x01F6 PK8579 ->P33]->[0x0203 PK8880 ->P34]->[normal own 4 0x0210 PK9181 ->P35]
    9       0x0244      node ptr    4       10385   67
    slot reference: [0x021D PK9482 ->P64]->[0x022A PK9783 ->P65]->[0x0237 PK10084 ->P66]->[normal own 4 0x0244 PK10385 ->P67]
    10      0x0278      node ptr    4       11589   71
    slot reference: [0x0251 PK10686 ->P68]->[0x025E PK10987 ->P69]->[0x026B PK11288 ->P70]->[normal own 4 0x0278 PK11589 ->P71]
    11      0x02AC      node ptr    4       12793   75
    slot reference: [0x0285 PK11890 ->P72]->[0x0292 PK12191 ->P73]->[0x029F PK12492 ->P74]->[normal own 4 0x02AC PK12793 ->P75]
    12      0x02E0      node ptr    4       13997   79
    slot reference: [0x02B9 PK13094 ->P76]->[0x02C6 PK13395 ->P77]->[0x02D3 PK13696 ->P78]->[normal own 4 0x02E0 PK13997 ->P79]
    
    ...

我们没有截取完整,这些数据已经够我们进行查找了。由于当前页的level不是0,则当前页不是leaf page,属于internal page,索引页的一种,和root index page区分开。

我们依旧来分析下页目录:

1. (无穷小, 753]
2. (753, 1957]
3. ...

我们来判断2落在哪个范围区间内,很容易得知在区间1内,当然程序内使用二分法是很快就能查找到的。范围1又有以下的页指向范围:

1. [1, 151) page 4
2. [151, 452) page 5
3. [452, 1054) page 6

我们要查找的2落在范围1中,所以我们需要查找page 4。

### 查找B+树叶子页

我们来看下page 4的page directory。

                            ==========PAGE 4 OFFSET 0x10000 LEVEL 0==========
    slot    offset      type        owned   key     page ptr
    0       0x0063      infimum     1       N/A     0
    slot reference: [infimum own 1 0x0063 ->P0]
    1       0x0113      normal      4       4       N/A
    slot reference: [0x007D PK1]->[0x00AF PK2]->[0x00E1 PK3]->[normal own 4 0x0113 PK4]
    2       0x01DB      normal      4       8       N/A
    slot reference: [0x0145 PK5]->[0x0177 PK6]->[0x01A9 PK7]->[normal own 4 0x01DB PK8]
    3       0x02A3      normal      4       12      N/A
    slot reference: [0x020D PK9]->[0x023F PK10]->[0x0271 PK11]->[normal own 4 0x02A3 PK12]
    4       0x036B      normal      4       16      N/A
    slot reference: [0x02D5 PK13]->[0x0307 PK14]->[0x0339 PK15]->[normal own 4 0x036B PK16]
    5       0x0433      normal      4       20      N/A
    ...

和分析non-leaf index page一样,leaf page也是需要先查找页目录:

1. (无穷小, 4]
2. (4, 8]
3. ...

很容易就能查到,我们要查找的在区间1内,于是命中了区间1的槽。

在这儿,叶子页和非叶子页就差生了区别,非叶子页的各个recorder,都是一个范围,而叶子页则是具体的行数据,假设我们在槽指向的recorder和它拥有的recorder中都没有查到该key,则这条记录就不存在了。

我们遍历该槽拥有的所有记录,发现了  ```[0x007D PK1]```,于是page 4偏移0x007D的位置就是我们要查找到的记录了。

### 小结

在查找某条记录的时候,我们首先应定为Root index page,并通过查找页目录的方式查找到recorder,并根据是否是叶子页来确定该recorder是否是具体的行数据还是指向下一页的recorder(其实recorder里标识来标识recorder的类型)。

你可能感兴趣的:(数据库)