use muse;
CREATE TABLE `tb_student` (
`id` bigint(20) NOT NULL COMMENT '自增主键',
`number` int NOT NULL DEFAULT '-1' COMMENT '学号',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int NOT NULL DEFAULT '-1' COMMENT '年龄',
PRIMARY KEY (`id`),
INDEX `index_number_age` (`number`,`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
INSERT INTO tb_student VALUES (3, 300, 'tom', 16);
INSERT INTO tb_student VALUES (1, 100, 'muse', 16);
INSERT INTO tb_student VALUES (4, 400, 'john', 18);
INSERT INTO tb_student VALUES (2, 200, 'bob', 17);
【说明】
答:这些被删除的记录之所以没有从磁盘上删除,是因为如果移除了,还需要在磁盘上重新排列剩余的记录,这会带来一定的性能消耗,所以只是打了一个删除的标记就可以避免重排。然后所有的被删除掉的记录会组成一个垃圾链表,记录在这个链表中占用的空间被称为可重用空间。之后若是有新的记录插入到表中,它们就可以覆盖掉被删除的这些记录占用的存储空间了
min_rec_flag:B+树中每层非叶子节点中的最小的目录项记录,都会添加该标记。
n_owned:一个页面被分若干组后,“带头大哥”用于保存组中所有的记录条数。
heap_no:表示当前记录在页面堆中的相对位置。
我们向表中插入的记录都会放到User Record部分,这些记录一条条的连续排列着,InnoDB将此连续排列的结构称之为堆(heap)。
为了方便管理,他们把一条记录在堆中的相对位置称之为heap_no。
因为创建页时,每个页会自动添加两条记录,且都没有主键值:一条代表页面中最小记录(即:比任何用户记录都小)——Infimum记录,heap_no=0;另一条代表页面中的最大记录(即:比任何用户记录都大)——Supremum记录,heap_no=1;为了区分这两条默认记录和用户自己插入的记录,将着两条记录放到一个称为Infimum+Supremum的部分。
0:普通记录;1: B+树非叶子节点的目录项记录;2:表示Infimum记录;3:表示Supremum记录
答:因为这个位置刚刚好,向左读取就是记录头信息;向右读取就是真实数据
;该属性为正数——说明当前记录的下一条记录在它的后面。该属性为负数——说明当前记录的下一条记录在它的前面。比如:一条记录的next_record值为32,意味着从当前记录的真实数据的地址处向后找32字节便是下一条记录的真实数据。其中:「下一条记录」指的是按主键值由小到大的顺序排列的下一条记录。
【分组规则如下】
【分组步骤如下】
第一步:通过二分法确定该记录所在分组对应的Slot,然后找到该Slot所在分组中主键值最小的那条记录。每个槽对应的都是组内主键值最大的记录,那么怎么定位一个组中主键值最小的记录呢?答:由于每个槽都是挨着的,所以,我们可以通过找到前一个槽中的最大主键值记录,这个记录的下一条记录(next_record),就是本槽的最小主键值记录。
第二步:通过记录的next_record属性遍历该槽所在组中的各个记录。
名称 |
大小 |
描述 |
PAGE_N_DIR_SLOTS |
2bits |
Page Directory中槽位的数量 |
PAGE_HEAP_TOP |
2bits |
未使用空间最小地址,从该地址之后就是Free Space |
PAGE_N_HEAP |
2bits |
第1位:本记录是否为紧凑型记录 剩余15位:本页的堆中记录的数量(包含Infimum和Supremun和标记删除记录) |
PAGE_FREE |
2bits |
每个已删除的记录通过next_record组成一个单向链表,他们的空间可以被重新利用,PAGE_FREE表示该链表头节点对应记录在页面中的偏移量 |
PAGE_GARBAGE |
2bits |
已删除记录占用的字节数 |
PAGE_LAST_INSERT |
2bits |
最后插入记录的位置 |
PAGE_DIRECTION |
2bits |
记录插入的方向【下面会有解释】 |
PAGE_N_DIRECTION |
2bits |
一个方向连续插入的记录数量【下面会有解释】 |
PAGE_N_RECS |
2bits |
该页中用户记录的数量(不包含Infimum和Supremum和被删除记录) |
PAGE_MAX_TRX_ID |
8bits |
修改当前页的最大事务id,该值仅在二级索引页面中定义 |
PAGE_LEVEL |
2bits |
当前页在B+树中所处的层级,从0层开始 |
PAGE_INDEX_ID |
8bits |
索引ID,表示当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF |
10bits |
B+树叶子节点段的头部信息,仅在B+树的根页面中定义 |
PAGE_BTR_SEG_TOP |
10bits |
B+树非叶子节点段的头部信息,仅在B+树的根页面中定义 |
假如新插入的一条记录的主键值比上一条记录的主键值大,我们就说这条记录插入方向是右边,反之则是左边。
假如连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记下来,这个条数就是PAGE_N_DIRECTION,如果最后一条记录的插入方向发生了改变,这个状态值就会被清零后重新统计。
名称 |
大小 |
描述 |
FIL_PAGE_SPACE_OR_CHKSUM |
4bits |
表示页的“校验和”(checksum)【下面会有解释】 |
FIL_PAGE_OFFSET |
4bits |
页号【下面会有解释】 |
FIL_PAGE_PREV |
4bits |
上一个页的页号【下面会有解释】 |
FIL_PAGE_NEXT |
4bits |
下一个页的页号【下面会有解释】 |
FIL_PAGE_LSN |
8bits |
页面被最后修改时对应的LSN值 |
FIL_PAGE_TYPE |
2bits |
该页的类型【下面会有解释】 |
FIL_PAGE_FILE_FLUSH_LSN |
8bits |
仅在系统表空间的第一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID |
4bits |
该页属于哪个表空间 |
就是将一个很长的字节串通过某种算法转变为较短的值来代表这个长的字节串,这个比较短的值就称为校验和(checksum)。这样在比较两个很长的字节串之前,先比较他们的checksum,如果不相同,则说明这两个字节串肯定是不同的,这样就省去了直接比较两个长字节串的时间损耗。
每个页都有一个独一无二的页号,如身份证一样。InnoDB通过页号来唯一定位一个页。
InnoDB为了不同的目的而把页分为不同的类型,我们前面存储记录的页类型是数据页/索引页。
页的类型如下所示:
名称 |
十六进制 |
描述 |
FIL_PAGE_TYPE_ALLOCATED |
0x0000 |
最新分配,还未使用 |
FIL_PAGE_UNDO_LOG |
0x0002 |
undo日志页 |
FIL_PAGE_INODE |
0x0003 |
存储段的信息 |
FIL_PAGE_IBUF_FREE_LIST |
0x0004 |
Change Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP |
0x0005 |
Change Buffer的一些属性 |
FIL_PAGE_TYPE_SYS |
0x0006 |
存储一些系统数据 |
FIL_PAGE_TYPE_TRX_SYS |
0x0006 |
事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR |
0x0008 |
表空间头部信息 |
FIL_PAGE_TYPE_XDES |
0x0009 |
存储区的一些属性 |
FIL_PAGE_TYPE_BLOB |
0x000A |
溢出页 |
FIL_PAGE_INDEX |
0x45BF |
索引页,也就是我们所说的数据页 |
通过这两个参数,就可以建立一个双向链表把许多的页串联起来,而无须这些页在物理上是真正紧挨在一起的。这里需要注意的是,并不是所有类型的页都有这两个属性的。
当一个页在内存中被修改时,在刷新到磁盘之前首先是要计算出checksum值的。由于File Header在页面的前边,所以File Header中的checksum会被优先刷新到磁盘,当完全写完后,checksum的值再被写到File Trailer。如果页面刷新成功,那么File Header和File Trailer的checksum值应该是一致的。否则,就意味着刷新期间发生了错误。
正常情况下File Trailer的这部分值应该与File Header的FIL_PAGE_LSN的后4的字节相同。这部分也是用于校验页的完整性的。