MySQL索引(进阶篇)

后面也会持续更新,学到新东西会在其中补充。

建议按顺序食用,欢迎批评或者交流!

缺什么东西欢迎评论!我都会及时修改的!

大部分截图和文章采用该书,谢谢这位大佬的文章,在这里真的很感谢让迷茫的我找到了很好的学习文章。我只是加上了自己的拙见我只是记录学习没有任何抄袭意思

 MySQL 是怎样运行的:从根儿上理解 MySQL - 小孩子4919 - 掘金小册

MySQL技术内幕:InnoDB存储引擎(第2版) (数据库技术丛书) (姜承尧) 

操作环境

MySQL9.0.1 

前言

 InnoDB数据页的7个组成部分,知道了各个数据页可以组成一个双向链表,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

没有索引的查找

对某个列精确匹配的情况,所谓精确匹配(=)。 

SELECT [列名列表] FROM 表名 WHERE 列名 = xxx;

 在一个页中的查找

假设目前表中的记录比较少,所有的记录都可以被存放到一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:

  • 主键为搜索条件

    页目录(Page Directory)中使用二分法快速定位到对应的槽(slot),然后再遍历该槽对应分组中的记录即可快速找到指定的记录。比如找到槽2,通过槽1的next_record遍历槽2的所有记录,找到对应主键值。

  • 以其他列作为搜索条件

    在数据页中并没有对非主键列建立所谓的页目录,所以我们无法通过二分法快速定位相应的。这种情况下只能从最小记录开始依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件。很显然,这种查找的效率是非常低的。(全表扫描)

在很多页中查找

大部分情况下我们表中存放的记录都是非常多的需要好多的数据页来存储这些记录。在很多页中查找记录的话可以分为两个步骤:

  1. 定位到记录所在的页。
  2. 从所在的页内中查找相应的记录。

在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据我们刚刚唠叨过的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方式显然是超级耗时的,如果一个表有一亿条记录,使用这种方式去查找记录需要非常耗时。因此有了索引

索引

mysql> CREATE TABLE index_demo(
     c1 INT,
     c2 INT,
     c3 CHAR(1),
     PRIMARY KEY(c1)
 ) ROW_FORMAT = Compact;

这个新建的index_demo表中有2个INT类型的列,1个CHAR(1)类型的列,而且我们规定了c1为主键,这个表使用Compact行格式来实际存储记录的。 

 这些之前都讲过了这里简单过一下

  • record_type:记录头信息的一项属性,表示记录的类型,0表示普通记录、2表示最小记录、3表示最大记录。

  • next_record:记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,为了方便大家理解,我们都会用箭头来表明下一条记录是谁。

  • 各个列的值:这里只记录在index_demo表中的三个列,分别是c1c2c3

  • 其他信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

省去其他信息

 把一些记录放到页里边的示意图就是:

记录之间通过next_record连接起来。

一个简单的索引方案

为了解决之前的记录不知道在哪个页面,其实很简单一句话,就是为页面建页目录

下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。 

先从代码层面看一下,这是我在 《MySQL技术内幕:InnoDB存储引擎(第2版) (数据库技术丛书) (姜承尧) 》 书中找的例子。

 可以看到主键是递增的 1 -> 2。

此时index_demo有三条记录

 当前表中 主键有 1 3 5

此时插入主键为 4 的记录。

假如页10只能放3行记录,所以不得不再分配一个新页:

 页号不一定是连续的,只是通过FIL_PAGE_PREV 和 FIL_PAGE_NEXT 把页和页之间联系起来。

还有一个问题!主键 4 < 5 需要把主键5 从 页10 拿出放到 页28 ,主键4 从 页28 拿出放到 页10。

也就是页分裂问题!

 给所有的页建立一个目录项。

index_demo表中插入许多条记录后。

 因为这些16KB的页在物理存储上可能并不挨着,所以如果想从这么多页中根据主键值快速定位某些记录所在的页,我们需要给它们做个目录每个页对应一个目录项,每个目录项包括下边两个部分:

  • 页的用户记录中最小的主键值,我们用key来表示。
  • 页号,我们用page_no表示。

页28为例,它对应目录项2,这个目录项中包含着该页的页号28以及该页中用户记录的最小主键值5。我们只需要把几个目录项在物理存储器上连续存储,比如把他们放到一个数组里,就可以实现根据主键值快速查找某条记录的功能了。比方说我们想找主键值为20的记录,具体查找过程分两步:

  1. 先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12 < 20 < 209),它对应的页是页9
  2. 再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

InnoDB中的索引方案

简易的索引方案的问题:

  • InnoDB是使用页来作为管理存储空间的基本单位,也就是最多能保证16KB的连续存储空间,而随着表中记录数量的增多,需要非常大的连续的存储空间才能把所有的目录项都放下,这对记录数量非常多的表是不现实的。

  • 我们时常会对记录进行增删,假设我们把页28中的记录都删除了,页28也就没有存在的必要了,那意味着目录项2也就没有存在的必要了,这就需要把目录项2后的目录项都向前移动一下,这种牵一发而动全身的设计不好!

为了和用户记录做一下区分,我们把这些用来表示目录项的记录称为目录项记录

通过record_type属性来区分,普通记录为0,目录项记录为1。 

 再把这张图掏出来

00    null列表
1     delete_mask = 0 min_rec_mask = 1
00 11 heap_no = 2 recor_type = 1 验证了之前的说法
00 0e next_record = 14

 新分配了一个编号为30的页来专门存储目录项记录

目录项记录和普通的用户记录的不同点:

  • 目录项记录record_type值是1,而普通用户记录的record_type值是0。

  • 目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列,另外还有InnoDB自己添加的隐藏列。

  • 还记得我们之前在唠叨记录头信息的时候说过一个叫min_rec_mask的属性么,只有在存储目录项记录的页中的主键值最小的目录项记录min_rec_mask值为1,其他别的记录的min_rec_mask值都是0

  • 页面类型都是0x45BF,这个属性在File Header

  • 页的组成结构也是一样(前边介绍过的7个部分),都会为主键值生成Page Directory(页目录),从而在按照主键值进行查找时可以使用二分法来加快查询速度。

现在以查找主键为20的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:

  1. 先到存储目录项记录的页,也就是30中通过二分法快速定位到对应目录项记录,因为12 < 20 < 209,所以定位到对应的记录所在的页就是页9

  2. 再到存储用户记录的页9中根据二分法快速定位到主键值为20的用户记录。

一个页只有16KB大小,能存放的目录项记录也是有限的,表中数据过多会再申请一个页

从图中可以看出,我们插入了一条主键值为320的用户记录之后需要两个新的数据页:

  • 为存储该用户记录而新生成了页31

  • 因为原先存储目录项记录页30的容量已满(我们前边假设只能存储4条目录项记录),所以不得不需要一个新的页32来存放页31对应的目录项。

 现在因为存储目录项记录的页不止一个,所以如果我们想根据主键值查找一条用户记录大致需要3个步骤,以查找主键值为20的记录为例:

  1. 确定目录项记录

    我们现在的存储目录项记录的页有两个,即页30页32,又因为页30表示的目录项的主键值的范围是[1, 320)页32表示的目录项的主键值不小于320,所以主键值为20的记录对应的目录项记录在页30中。通过 Page Directory 中的槽判断主键是否满足,如当前页不满足,走下一个页。

  2. 通过目录项记录页确定用户记录真实所在的页。通过 Page Directory 中的槽找到对应页号。

  3. 在真实存储用户记录的页中定位到具体的记录。通过 Page Directory 中的槽找到对应记录。

第1步中我们需要定位存储目录项记录的页,但是这些页在存储空间中也可能不挨着,如果我们表中的数据非常多则会产生很多存储目录项记录的页,那我们怎么根据主键值快速定位一个存储目录项记录的页呢?

答案就是目录套目录!

 如图,生成了一个存储更高级目录项的页33,这个页中的两条记录分别代表页30页32,如果用户记录的主键值在[1, 320)之间,则到页30中查找更详细的目录项记录,如果主键值不小于320的话,就到页32中查找更详细的目录项记录

最终成为了B+树!

数据页为节点

实际用户记录都存放叶子节点叶节点

其中B+树最上边的那个节点也称为根节点

总结一下这个过程(假如树高为2):找目录(Page Directory)二分判断主键在哪个槽,通过主键找到页号,找目录(Page Directory)二分判断主键在哪个槽,通过主键找到记录。

聚簇索引

B+树本身就是一个目录,或者说本身就是一个索引。 

使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:

  • 页内的记录是按照主键的大小顺序排成一个单向链表。(next_record)

  • 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。如下图:

 

  • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表

 B+树的叶子节点存储的是完整的用户记录

所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

InnoDB存储引擎会自动的为我们创建聚簇索引。另外有趣的一点是,在InnoDB存储引擎中,聚簇索引就是数据的存储方式(所有的用户记录都存储在了叶子节点),也就是所谓的索引即数据,数据即索引。

”你即是我,我即是你。“

二级索引

聚簇索引只能在搜索条件是主键值时才能发挥作用 

不同的B+树中的数据采用不同的排序规则。比方说我们用c2列的大小作为数据页、页中记录的排序规则,再建一棵B+树,效果如下图所示: 

这个B+树与上边介绍的聚簇索引有几处不同: 

  • 使用记录c2列的大小进行记录和页的排序,这包括三个方面的含义:

    • 页内的记录是按照c2列的大小顺序排成一个单向链表

    • 各个存放用户记录的页也是根据页中记录的c2列大小顺序排成一个双向链表

    • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表

  • B+树的叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两个列的值。(非常重要!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)

  • 目录项记录中不再是主键+页号的搭配,而变成了c2列+页号的搭配。

查找c2列的值为4的记录为例,查找过程如下: 

  1. 确定目录项记录

    根据根页面,也就是页44,可以快速定位到目录项记录所在的页为页42(因为2 < 4 < 9)。

  2. 通过目录项记录页确定用户记录真实所在的页。

    页42中可以快速定位到实际存储用户记录的页,但是由于c2列并没有唯一性约束,所以c2列值为4的记录可能分布在多个数据页中,又因为2 < 4 ≤ 4,所以确定实际存储用户记录的页在页34页35中。

  3. 在真实存储用户记录的页中定位到具体的记录。

    页34页35中定位到具体的记录

  4. 但是这个B+树的叶子节点中的记录只存储了c2c1(也就是主键)两个列,所以我们必须再根据主键值去聚簇索引中再查找一遍完整的用户记录

步骤4的操作,c2列大小排序的B+树只能确定我们要查找记录的主键值,所以如果我们想根据c2列的值查找到完整的用户记录的话,仍然需要到聚簇索引中再查一遍,这个过程也被称为回表。也就是根据c2列的值查询一条完整的用户记录需要使用到2B+树!!!

如果把完整的用户记录放到叶子节点是可以不用回表,但是要节省空间!

非主键列建立的B+树需要一次回表操作才可以定位到完整的用户记录,所以这种B+树也被称为二级索引(英文名secondary index),或者辅助索引

联合索引

 同时为多个列建立索引,比方说我们想让B+树按照c2c3的大小进行排序,这个包含两层含义:

  • 先把各个记录和页按照c2进行排序。
  • 在记录的c2相同的情况下,采用c3进行排序

  • 每条目录项记录都由c2c3页号这三个部分组成,各条记录先按照c2的值进行排序,如果记录的c2相同,则按照c3的值进行排序。

  • B+树叶子节点处的用户记录由c2c3主键c1列组成。

 以c2和c3列的大小为排序规则建立的B+树称为联合索引,本质上也是一个二级索引

  • 建立联合索引只会建立如上图一样的1棵B+

create index idx_c2_c3 on index_demo(c2,c3);
  • c2c3列分别建立索引会分别以c2c3的大小为排序规则建立2棵B+

create index idx_c2 on index_demo(c2);
create index idx_c3 on index_demo(c3);

InnoDB的B+树索引的注意事项

根页面万年不动窝 

实际上B+树的形成过程是这样的:

  • 每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点既没有用户记录,也没有目录项记录

  • 随后向表中插入用户记录时,先把用户记录存储到这个根节点中。

  • B+树根节点创建后就不会改变,之后写入记录超过一页16K就会页拷贝+页分裂,根节点升级为页记录项目录,两个子节点页依旧按照主键(索引列记录大小)排序,record_type==1时为目录记录项,min_rec_mask==1时为非叶子结点最小记录,即每一页数据页中最小的那个值记录到目录记录项里,查找时根据根节点二分即可。

我其实不太明白为什么会触发页分裂,之前说的是主键大小没有按顺序会触发页分裂

每个页都有固定的大小,只能容纳一定数量的记录。当一个页满了,也会触发页分裂,根节点已经存满,cody到页a以后,页a还需要页分裂到页b。

一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。

存储某个索引的根节点在哪个页面中的信息就是传说中的数据字典中的一项信息待续

 内节点中目录项记录的唯一性

B+树索引的内节点中目录项记录的内容是索引列 + 页号的搭配,但是这个搭配对于二级索引来说有点儿不严谨。还拿index_demo表为例,假设这个表中的数据是这样的:

c1c2c3
11'u'
31'd'
51'y'
71'a'

 如果二级索引中目录项记录的内容只是索引列 + 页号的搭配的话,那么为c2列建立索引后的B+树应该长这样:

 如果我们想新插入一行记录,其中c1c2c3的值分别是:91'c',那么在修改这个为c2列建立的二级索引对应的B+树时便碰到了个大问题:由于页3中存储的目录项记录是由c2列 + 页号的值构成的,页3中的两条目录项记录对应的c2列的值都是1。

而我们新插入的这条记录的c2的值也是1,那我们这条新插入的记录到底应该放到页4中,还是应该放到页5中啊?

主键值也添加到二级索引内节点中的目录项记录了,这样就能保证B+树每一层节点中各条目录项记录除页号这个字段外是唯一的。

根据主键判断放到页5中。

一个页面最少存储2条记录

这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录。那如果一个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常非常多,而且最后的那个存放真实数据的目录中只能存放一条记录。所以InnoDB的一个数据页至少可以存放两条记录。

个人抽象的理解

环境搭建

baiwfg2/py_innodb_page_info: python tool for innodb page info

python3 

yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel

cd /opt
wget https://www.python.org/ftp/python/3.10.5/Python-3.10.5.tgz
tar -zxvf Python-3.10.5.tgz

cd Python-3.10.5/
./configure --with-ssl
make 
make install

ln -s /usr/local/bin/python3 /usr/bin/python

[root@CentOS8 bin]# python -V
Python 3.10.5
unzip py_innodb_page_info-master.zip
mv py_innodb_page_info-master py_innodb_page_info
cd py_innodb_page_info/

mkdir ibds //把要想解析的数据表放在其中

[root@CentOS8 py_innodb_page_info]# python py_innodb_page_info.py ibds/type.ibd
Total number of page: 8:
File Space Header: 1
Insert Buffer Bitmap: 1
File Segment inode: 1
Tablespace SDI index Page: 1
B-tree Node: 3
Freshly Allocated Page: 1
You have new mail in /var/spool/mail/root

[root@CentOS8 py_innodb_page_info]# python py_innodb_page_info.py ibds/type.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <Tablespace SDI index Page>
page offset 00000004, page type <B-tree Node>, page level <0000>
page offset 00000005, page type <B-tree Node>, page level <0000>
page offset 00000006, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 8:
File Space Header: 1
Insert Buffer Bitmap: 1
File Segment inode: 1
Tablespace SDI index Page: 1
B-tree Node: 3
Freshly Allocated Page: 1

参考文献:《MySQL技术内幕:InnoDB存储引擎(第2版) (数据库技术丛书) (姜承尧) 》 

py_innodb_page_info.py工具使用-优快云博客

page offset

该页在表空间的偏移量;

00000003页:3 * 16KB = 49152 = 0xc000,

打开.ibd文件,找到0xc000,就是00000003页的起始位置。(重点)

page type

页的类型,其中:

B-tree Node:B+树节点页,page level <0000>是叶子节点;

                                              其他是非叶子节点;

    Freshly Allocated Page:最新分配的页(空闲页);

    Insert Buffer Bitmap:插入缓冲Bitmap页;

page level

 B+树的高度;

 <0000>是叶子节点;非0是非叶子节点(重点)

Total number of page表空间的页总数
Freshly Allocated Page表空间的空闲页总数

Insert Buffer Bitmap

插入缓冲Bitmap的页数
File Space HeaderFile Space Header的页数
B-tree Node B+树节点的页数量(叶子节点 + 非叶子节点)
File Segment inodeFile Segment inode的页数

 以下截图采用该书《MySQL技术内幕:InnoDB存储引擎(第2版) (数据库技术丛书) (姜承尧) 》 

[root@nineyou0-43 mytest]#py_innodb_page_info.py-v t.ibd
page offset 00000000,page type<File Space Header>
page offset 00000001,page type<Insert Buffer Bitmap>
page offset 00000002,page type<File Segment inode>
page offset 00000003,page type<B-tree Node>,page level<0000>
page offset 00000000,page type<Freshly Allocated Page>
page offset 00000000,page type<Freshly Allocated Page>
Total number of page:6:
Freshly Allocated Page:2
Insert Buffer Bitmap:1
File Space Header:1
B-tree Node:1
File Segment inode:1

page offset 00000003

3 * 16kb = 49152(10进制) = c000(16进制)

数据页从0x0000c000处开始

实验

聚簇索引实验

drop table t;
CREATE TABLE t(
a INT NOT NULL,
b VARCHAR(8000),
PRIMARY KEY(a)
)ENGINE=INNODB;
INSERT INTO t SELECT 1,REPEAT('a',7000);
INSERT INTO t SELECT 2,REPEAT('a',7000);
INSERT INTO t SELECT 3,REPEAT('a',7000);
INSERT INTO t SELECT 4,REPEAT('a',7000);
[root@CentOS8 py_innodb_page_info]# python py_innodb_page_info.py -v /root/py_innodb_page_info/ibds/t.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <Tablespace SDI index Page>
page offset 00000004, page type <B-tree Node>, page level <0001> 叶子节点 聚簇索引4
page offset 00000005, page type <B-tree Node>, page level <0000> 非叶子节点 数据页5
page offset 00000006, page type <B-tree Node>, page level <0000> 非叶子节点 数据页6
page offset 00000007, page type <B-tree Node>, page level <0000> 非叶子节点 数据页7
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 9:
File Space Header: 1
Insert Buffer Bitmap: 1
File Segment inode: 1
Tablespace SDI index Page: 1
B-tree Node: 4
Freshly Allocated Page: 1


page level为0000的即是数据页
page level为0001的页,当前聚集索引的B+树高度为2,故该页是B+树的根。

 插入的列b长度为7000,因此可以以人为的方式使目前每个页只能存放两个行记录。

聚簇索引包含<主键,页号

4 x 1024 x 16 = 0001 0000 

页号为00 00 00 04 = 页号4

File Header(38)
00010000  84 dd 94 d3 00 00 00 04  ff ff ff ff ff ff ff ff  |................|
00010010  00 00 00 00 06 a9 1c 2e  45 bf 00 00 00 00 00 00  |........E.......|
  00 00 00 00 00 57
Page Header(56)
00010020  00 02  00 a2 80 05 00 00 00 00  |.....W..........|
00010030  00 9a 00 02 00 02 00 03  00 00 00 00 00 00 00 00  |................|
00010040  00 01 00 00 00 00 00 00  01 0c 00 00 00 57 00 00  |.............W..|
  00 02 02 72 00 00 00 57  00 00 00 02 01 b2 
infimum 和 supremum (26)
00010050  01 00  |...r...W........|
00010060  02 00 1b 69 6e 66 69 6d  75 6d 00 04 00 0b 00 00  |...infimum......|
  73 75 70 72 65 6d 75 6d 
00010070  00 10 00 11 00 0e 80 00  |supremum........|
00010080  00 01 00 00 00 05 00 00  00 19 00 0e 80 00 00 02  |................|
00010090  00 00 00 06 00 00 00 21  ff d6 80 00 00 04 00 00  |.......!........|
000100a0  00 07 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
#最小记录
01 00 02 00 1b 
0        m = 0 d = 0
1        n = 1
00 02    h = 0 r = 2
00 1b    n = 27

63 + 1b = 7e 
80 00 00 01 主键1
00 00 00 05 页号5
80 00 00 02 主键2
00 00 00 06 页号6
80 00 00 04 主键4
00 00 00 07 页号7

 5 x 1024 x 16 = 0001 4000

页号为00 00 00 05 = 页号5

File Header
00014000  cb f6 36 65 00 00 00 05  ff ff ff ff 00 00 00 06  |..6e............|
00014010  00 00 00 00 06 a9 1c 2e  45 bf 00 00 00 00 00 00  |........E.......|
00014020  00 00 00 00 00 57 
Page Header
00 02  37 5a 80 04 1b f1 1b 71  |.....W..7Z.....q|
00014030  00 00 00 05 00 00 00 01  00 00 00 00 00 00 00 00  |................|
00014040  00 00 00 00 00 00 00 00  01 0c 00 00 00 00 00 00  |................|
00014050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 
最大记录和最小记录
01 00  |................|
00014060  02 00 1d 69 6e 66 69 6d  75 6d 00 02 00 0b 00 00  |...infimum......|
00014070  73 75 70 72 65 6d 75 6d  
数据
58 9b 00 00 00 10 ff f0  |supremumX.......|
00014080  80 00 00 01 00 00 00 01  c1 55 82 00 00 00 93 01  |.........U......|
00014090  10 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |.aaaaaaaaaaaaaaa|
000140a0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
.....

6 x 1024 x 16 = 0001 8000

页号为00 00 00 06 = 页号6

File Header
00018000  b0 11 d5 ce 00 00 00 06  00 00 00 05 00 00 00 07  |................|
00018010  00 00 00 00 06 a9 1c 2e  45 bf 00 00 00 00 00 00  |........E.......|
00018020  00 00 00 00 00 57 
Page Header
00 02  37 5a 80 04 00 00 00 00  |.....W..7Z......|
00018030  1b f1 00 05 00 00 00 02  00 00 00 00 00 00 00 00  |................|
00018040  00 00 00 00 00 00 00 00  01 0c 00 00 00 00 00 00  |................|
00018050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 
最大记录最小记录
01 00  |................|
00018060  02 00 1d 69 6e 66 69 6d  75 6d 00 03 00 0b 00 00  |...infimum......|
00018070  73 75 70 72 65 6d 75 6d  
数据
58 9b 00 00 00 10 1b 71  |supremumX......q|
00018080  80 00 00 02 00 00 00 01  c1 56 81 00 00 00 94 01  |.........V......|
00018090  10 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |.aaaaaaaaaaaaaaa|
000180a0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|

7 x 1024 x 16 =  0001 c000

页号为00 00 00 07 = 页号7

File Header
0001c000  d2 c8 67 35 00 00 00 07  00 00 00 06 ff ff ff ff  |..g5............|
0001c010  00 00 00 00 06 a9 1c 2e  45 bf 00 00 00 00 00 00  |........E.......|
0001c020  00 00 00 00 00 57 
Page Header
00 02  1b e9 80 03 00 00 00 00  |.....W..........|
0001c030  00 80 00 05 00 00 00 01  00 00 00 00 00 00 00 00  |................|
0001c040  00 00 00 00 00 00 00 00  01 0c 00 00 00 00 00 00  |................|
0001c050  00 00 00 00 00 00 00 00  00 00 00 00 00 00
最大记录最小记录
 01 00  |................|
0001c060  02 00 1d 69 6e 66 69 6d  75 6d 00 02 00 0b 00 00  |...infimum......|
0001c070  73 75 70 72 65 6d 75 6d 
数据
 58 9b 00 00 00 10 ff f0  |supremumX.......|
0001c080  80 00 00 04 00 00 00 01  c1 5a 82 00 00 01 91 01  |.........Z......|
0001c090  10 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |.aaaaaaaaaaaaaaa|
0001c0a0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|

min_rec_mask 验证

#第一行记录
00 10 00 11 00 0e 80 00 00 01 00 00 00 05
#第二行记录
00 00 00 19 00 0e 80 00 00 02 00 00 00 06
#第三行记录
00 00 00 21 ff d6 80 00 00 04 00 00 00 07

#关注第一行记录
00 null列表
1  代表的就是min_rec_mask后面记录都没有。
也就验证了B+树的每层非叶子节点中的最小记录都会添加该标记

 FIL_PAGE_PREV 和 FIL_PAGE_NEXT验证

页号5 
FIL_PAGE_PREV ff ff ff ff 上一页都是ff代表啥也没有
FIL_PAGE_NEXT 00 00 00 06 下一页为6
页号6
FIL_PAGE_PREV 00 00 00 05 上一页
FIL_PAGE_NEXT 00 00 00 07 下一页
页号6
FIL_PAGE_PREV 00 00 00 06 上一页
FIL_PAGE_NEXT ff ff ff ff 下一页

图片是这样的,虽然数据对不太上,但是结构是一样的! 第一页和最后一页没有连接关系。

二级索引实验

drop table t;
CREATE TABLE t(
a INT NOT NULL,
b VARCHAR(8000),
c INT NOT NULL,
PRIMARY KEY(a),
KEY idx_c(c)
)ENGINE=INNODB;
INSERT INTO t SELECT 1,REPEAT('a',7000),-1;
INSERT INTO t SELECT 2,REPEAT('a',7000),-2;
INSERT INTO t SELECT 3,REPEAT('a',7000),-3;
INSERT INTO t SELECT 4,REPEAT('a',7000),-4;

#若没有出现就自己创建一下
drop index idx_c on t;
create index idx_c on t(c);
[root@CentOS8 py_innodb_page_info]# python py_innodb_page_info.py -v /root/py_innodb_page_info/ibds/t.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <Tablespace SDI index Page>
page offset 00000004, page type <B-tree Node>, page level <0001>聚簇索引4
page offset 00000005, page type <B-tree Node>, page level <0000>二级索引5
page offset 00000006, page type <B-tree Node>, page level <0000>数据页6
page offset 00000007, page type <B-tree Node>, page level <0000>数据页7
page offset 00000008, page type <B-tree Node>, page level <0000>数据页8
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 10:
File Space Header: 1
Insert Buffer Bitmap: 1
File Segment inode: 1
Tablespace SDI index Page: 1
B-tree Node: 5
Freshly Allocated Page: 1

4 x 1024 x 16 = 0001 0000

页号为00 00 00 04 = 页号4

file header
00010000  3c 23 87 24 00 00 00 04  ff ff ff ff ff ff ff ff  |<#.$............|
00010010  00 00 00 00 06 aa 56 e2  45 bf 00 00 00 00 00 00  |......V.E.......|
00010020  00 00 00 00 00 58 
page header
00 02  00 a2 80 05 00 00 00 00  |.....X..........|
00010030  00 9a 00 02 00 02 00 03  00 00 00 00 00 00 00 00  |................|
00010040  00 01 00 00 00 00 00 00  01 0d 00 00 00 58 00 00  |.............X..|
00010050  00 02 02 72 00 00 00 58  00 00 00 02 01 b2
最大最小记录
01 00  |...r...X........|
00010060  02 00 1b 69 6e 66 69 6d  75 6d 00 04 00 0b 00 00  |...infimum......|
00010070  73 75 70 72 65 6d 75 6d  
数据
00 10 00 11 00 0e 80 00  |supremum........|
00010080  00 01 00 00 00 06 00 00  00 19 00 0e 80 00 00 02  |................|
00010090  00 00 00 07 00 00 00 21  ff d6 80 00 00 04 00 00  |.......!........|
000100a0  00 08 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

我们知道二级索引

叶子节点存放的<主键,索引列>

非叶子存放的<索引列,主键,页号>

5 x 1024 x 16 = 0001 4000

页号为00 00 00 05 = 页号5

file header
00014000  f1 80 a6 8c 00 00 00 05  ff ff ff ff ff ff ff ff  |................|
00014010  00 00 00 00 06 aa 98 20  45 bf 00 00 00 00 00 00  |....... E.......|
00014020  00 00 00 00 00 58 
page header
00 02  00 ac 80 06 00 00 00 00  |.....X..........|
00014030  00 a4 00 02 00 00 00 04  00 00 00 00 00 01 c1 81  |................|
00014040  00 00 00 00 00 00 00 00  01 0f 00 00 00 58 00 00  |.............X..|
00014050  00 02 03 f2 00 00 00 58  00 00 00 02 03 32 
最大记录最小记录
01 00  |.......X.....2..|
00014060  02 00 1a 69 6e 66 69 6d  75 6d 00 05 00 0b 00 00  |...infimum......|
00014070  73 75 70 72 65 6d 75 6d  
数据
00 00 10 00 0d 7f ff ff  |supremum........|
00014080  fc 80 00 00 04 00 00 18  00 0d 7f ff ff fd 80 00  |................|
00014090  00 03 00 00 20 00 0d 7f  ff ff fe 80 00 00 02 00  |.... ...........|
000140a0  00 28 ff cc 7f ff ff ff  80 00 00 01 00 00 00 00  |.(..............|
第一条记录
7f ff ff fc 80 00 00 04
7f ff ff fc c3索引值
80 00 00 04 主键值4
第二条记录
7f ff ff fd 80 00 00 03
7f ff ff fd c3索引值
80 00 00 04 主键值3
第三条记录
7f ff ff fe 80 00 00 02
7f ff ff fe c3索引值
80 00 00 02 主键值2
第四条记录
7f ff ff ff 80 00 00 01
7f ff ff ff c3索引值
80 00 00 01 主键值1

数据不一样但是结构差不多是这样 

剩下的道理都是差不多我就不放代码啦(太懒了)

我把我的文件放在这里有想看的同学可以看一下。

通过网盘分享的文件:idx_c.txt
链接: https://pan.baidu.com/s/1nKHv5AL6zCNEJor1G0jwLg?pwd=a788 提取码: a788

总结

所有结论都需要反复测试!如果有错误欢迎指正!一起努力!

如果喜欢的话,请点个赞吧就算鼓励我一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值