所在组;
? 我们一口气又往表中添加了12条记录,现在就一共有16条正常的记录了(包括最小和最大记录),这些记录被分成了5个组,如图所示:
? 上图中,只保留了头信息中的n_owned
和next_record
属性,也省略了各个记录之间的箭头,没画不等于没有!
? 因为各个槽代表的记录的主键值都是从小到大排序的,所以我们可以使用二分法
来进行快速查找。4个槽的编号分别是:0
、1
、2
、3
、4
,所以初始情况下最低的槽就是low=0
,最高的槽就是high=4
。比方说我们想找主键值为5
的记录,现在我们再来看看查找一条记录的步骤:
? 1. 首先得到中间槽的位置:(0 + 4)/2 = 2
,所以得到槽2,根据槽2的地址偏移量知道它的主键值是8,因为8>5,设置high=2
,low
不变;
? 2. 再次计算中间槽的位置:(0 + 2)/2 = 1
,所以得到槽1,根据槽1的地址偏移量知道它的主键值是4, 因为4<5,设置low=1
,high
不变;
? 3. 因为high - low
的值为1,所以确定主键值为5
的记录在槽1和槽2之间,接下来就是遍历链表的查找了;
?
? 所以在一个数据页中查找指定主键值的记录的过程分为两步:
1. 通过二分法确定该记录所在的槽。
2. 通过记录的next_record属性组成的链表遍历查找该槽中的各个记录。
? 设计InnoDB
的大叔们为了能得到一个数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,Page Directory
中存储了多少个槽等等,特意在页中定义了一个叫Page Header
的部分,它是页
结构的第二部分,这个部分占用固定的56
个字节,专门存储各种状态信息,具体各个字节都是干嘛的看下表:
?
PAGE_N_DIR_SLOTS |
2 |
在页目录中的槽数量 |
PAGE_HEAP_TOP |
2 |
第一个记录的地址 |
PAGE_N_HEAP |
2 |
本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE |
2 |
指向可重用空间的地址(就是标记为删除的记录地址) |
PAGE_GARBAGE |
2 |
已删除的字节数,行记录结构中delete_flag 为1的记录大小总数 |
PAGE_LAST_INSERT |
2 |
最后插入记录的位置 |
PAGE_DIRECTION |
2 |
最后插入的方向 |
PAGE_N_DIRECTION |
2 |
一个方向连续插入的记录数量 |
PAGE_N_RECS |
2 |
该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID |
2 |
修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL |
2 |
当前页在索引树中的位置,高度 |
PAGE_INDEX_ID |
8 |
索引ID,表示当前页属于哪个索引 |
PAGE_BTR |
10 |
非叶节点所在段的segment header,仅在B+树的Root页定义 |
PAGE_LEVEL |
10 |
B+树所在段的segment header,仅在B+树的Root页定义 |
? 如果大家认真看过前边的文章,那么大致能看明白这里头前边一半左右的状态信息的意思,剩下的状态信息看不明白不要着急,饭要一口一口吃,东西要一点一点学。在这里想强调以下PAGE_DIRECTION
和PAGE_N_DIRECTION
的意思。
PAGE_DIRECTION
假如新插入的一条记录的主键值比上一条记录的主键值比上一条记录大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就是PAGE_DIRECTION
。
PAGE_N_DIRECTION
假设连续几次插入新记录的方向都是一致的,InnoDB
会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION
这个状态表示。当然,如果最后一条记录的插入方向改变了的话,这个状态的值会被清零重新统计。
? 如果说Page Header
描述的是页
内的各种状态信息,比方说页里头有多少个记录了呀,有多少个槽了呀,那么File Header
描述的就是页
外的各种状态信息,比方说这个页的编号是多少,它的上一个页、下一个页是谁啦。File Header
是InnoDB
页的第一部分,这个部分占用固定的38
个字节,下边我们看看这个部分的各个字节都是代表啥意思吧:
FIL_PAGE_SPACE_OR_CHKSUM |
4 |
页的校验和(checksum值) |
FIL_PAGE_OFFSET |
4 |
页号 |
FIL_PAGE_PREV |
4 |
上一个页的页号 |
FIL_PAGE_NEXT |
4 |
下一个页的页号 |
FIL_PAGE_LSN |
8 |
最后被修改的日志序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE |
2 |
该页的类型(之前我们说的是数据页) |
FIL_PAGE_FILE_FLUSH_LSN |
8 |
仅在系统表空间的一个页中定义,代表文件至少被更新到了该LSN值,独立表空间中都是0 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID |
4 |
页属于哪个表空间 |
? 对照着这个表格,我们看几个目前比较重要的部分:
FIL_PAGE_SPACE_OR_CHKSUM
这个代表当前页面的校验和(checksum)。啥是个校验和?就是对于一个很长很长的字节串来说,我们会通过某种算法来计算一个值,这个值就称为校验和
。这样在比较两个很长的字节串之前先比较这两个长字节串的校验和,如果校验和都不一样两个长字节串肯定是不同的(hashCode和equals),所以省去了直接比较两个比较长的字节串的时间损耗(和后面的File Trailer里面的那个相对应,看到后面您就明白了)。
FIL_PAGE_OFFSET
每一个页
都有一个单独的页号,就跟您的身份证号码一样,InnoDB
通过页号来可以唯一定位一个页
。
FIL_PAGE_TYPE
这个代表当前页
的类型,我们前边说过,InnoDB
为了不同的目的而把页分为不同的类型,本集中介绍的其实都是存储记录的数据页
,其实还有很多别的类型的页:
FIL_PAGE_PREV
和FIL_PAGE_NEXT
一张表中可以有成千上万条记录,一个页只有16KB
,所以可能需要好多页来存放数据,FIL_PAGE_PREV
和FIL_PAGE_NEXT
就分别代表本页的上一个和下一个页的页号(双向链表)。
?
Page Header
的其它属性就不说了;
File Trailer
? 对于这个部分,我的理解比较简单,我们知道InnoDB
会把数据从内存刷新到磁盘,中间交互的单位是页 ,但是我们想想,假如再刷新到磁盘的时候出现了问题,这样的话怎么办呢?
? 这就是File Trailer
作用,这个部分由8
个字节组成,可以分成2个小部分:
- 前四个字节代表页的检验和:
- 这个部分是和
File Header
中的校验和相对应的。每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header
在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的