对于Mysql,我们再熟悉不过了,写SQL语句,创建索引,优化查询。
可能更深一点,我们知道了索引存储结构是B+树。
但是,我们从来没见过,这个B+树到底在哪了?我执行了一条创建索引的语句,它到底创建在哪了?
本文就为你揭开MySql磁盘存储的格式,带你看看索引到底长什么样!
注:本文只针对Innodb引擎。
Linux:/var/lib/mysql/xxxx.ibd
在Mysql中,数据的存储,使用space来进行管理。
我们知道innodb 默认 会开始file per table选项,也就是说你每创建一个表,就会生成对应的.ibd文件,可以简单理解为.ibd文件就是表数据。
space对应了具体的 .ibd文件,可能只对应一个,也可能包含多个ibd文件。不妨先认为一个space就是一个.ibd文件。
如果说space是Mysql中最大的单位,那么page就是Mysql中最小的单位了。
page默认单位是16kb,但是可以通过具体的参数进行调整。卖个关子,去Mysql官方文档自行查找如何调整page大小。
Other Data:根据page的类型,数据的内容也不一样
最熟悉的当然是索引页,也叫 index page,请记住这个页类型,因为它就是实际存储我们的索引数据的,一页16kb,貌似能存储不少的记录。
一个page只有16kb,很具体,但是太小,这时候Mysql引入了一个extent的概念,也就是区。
一个区由连续的64个page组成,大小为 64 x 16kb = 1 mb
与其说是区的结构,倒不如说是page的集合。毕竟区是由page组成的,能有什么结构呢?
对应着一个区,引入另一个概念,XDES Entry。
什么是XDES Entry?extent describe entey. 也就是对一个区结构的描述。这不是打脸么?上一秒还说区没有结构,是由page组成的,现在又说有一个什么XDES Entry来描述区结构。
我们试想:extent作为64个page的大哥,肯定是要知道page的具体信息的。比如哪个page的数据存满了,哪个page是空的?
这就是XDES Entry负责的内容,它用来记录extent内page的信息。也可以当做是extent的结构吧。
除此之外,XDES Entry还和segment有关系,这点我们稍后再讲。
File Segment ID:该区属于哪个file segment。暂时忽略,后面会讲到这是什么。
List Node:因为extent不只一个,那肯定会有很多XDES Entry,List Node就是连接XDES Entry链表的指针。里面有 pre 和 next。
State:状态,表明该extent内的page是否已经存储满了,什么意思?一个 extent有64个page,如果该extent所有的page都是空闲的,那么就是FREE,如果部分空闲,就是FREE_FRAG,如果满了,就是FULL_FRAG。
如果是属于某个segment,那么就是FSEG,表明该区属于某个segment,关于segment 后面会描述。
Page State bitmap:区内的哪个page是空闲的。
可能考虑成如下这样,#代表page已经有数据,.代表空闲,那么 Page state bitmap就好像这样:
###....###...#####.######
在上一节以及上上一节,我们讲了page,讲了extent,讲了extent的管理单元XDES Entry。
其中page里面我们提到有一种page类型是FSP HEADER/XDES page。
没错,XDES Entry就存在于这种类型的page中。
为什么要叫FSP HEADER/XDES page呢?看着是两种类型的page。其实确实是两种page,但是基本上都是一样的,所有统一称为FSP HEADER/XDES page。
对比一下最开始的 page 的结构,必不可少的是FIL Header和FIL Tralier。
然后剩下的就是XDES Entry。值得注意的是在该page还有一个FSP Header,在普通的XDES page中该FSP Header没有任何值。这就是FSP HEADER和XDES page的唯一区别。
在一个space内,该FSP HEADER page只存在一个,位于空间第一页。存储关于整个空间的信息。除此之外,其他的都是XDES page。
每个XDES page一共有256个XDES Entry,我们来做个计算:
1个XDES Entry 描述一个 extent,也就是1M,256个也就是256M,
也就是说一个 XDES page可以管理256M的空间。
如果把上面的space extent page串起来,就像下面这样:
space是最大的,包含了extent,在extent内有64个page。其中第一个page是FSP Header page,描述了该空间的信息。其他的IBUF_BITMAP先不用关心。
我们已经介绍了 space extent 和 page。
这些都是物理上真是存在的东西,你看我们都是通过大小来描述的,一个page是64k,一个extent是1M。
其实还有一种单位,那就是segment。segment称为段,一种逻辑上的单位。
有点抽象了!segment是什么?它在哪里?它的大小有多大?
我们说到,segment是一个逻辑上的单位,他只是理论上存在的。怎么说?
int[] array = new int[0,1,2,3,4,5];
整个数组你想象成一个space,想象0和1是一个extent,0,1,2等这些是page。
segment可以想象成指向某几个extent的指针。可以认为多个extent组成了segment。
注意,segment又称为 file segment, 简称FSEG。
就像XDES Entry是extent的管理单元一样,INODE 就是segment的管理单元。
FSEG ID:该INODE 描述的 segment 的 id
segment由多个extent和32个空闲的page组成。之前不是说segment是由extent组成的吗?怎么这会又说还有32个空闲的page?这其实是Mysql的一种优化措施,如果一个segment上去就分配一个64page的extent,可能太过于浪费,或者还有其他的碎片的page没有利用,那么Mysql就是先分配给segment 碎片page,等到塞满32个空闲的page后,才会分配整个extent。
FREE:segment下extent内全都是空闲的page的集合
FULL:segment下extent内全都是用过的page的集合
NOT_FULL:segment下extent部分空闲的page的集合
List Base Node for FREE list:两个指针,指向FREE 集合的头和尾
List Base Node for FULL list:两个指针,指向FULL 集合的头和尾
List Base Node for NOT_FULL list:两个指针,指向NOT_FULL 集合的头和尾
Fragment Array:最开始为segment分配空间时,不会一下分配一个extent,那样64页太多了,会先分配一个page,直到分配了32个page后,如果还要分配,那么就一次分配一个extent,到达一定次数后,最后每四个四个分配。
介绍完了segment是逻辑上的空间,以及它的INODE的结构,那么INODE存在哪呢?
细心的读者可能会发现,在上面的关于space extent page的小结的图中,第三个page,它的名字叫INODE。
可以看到,一个page中有85个 INODE,能够表示85个segment。
INODE page 位于space的第三个位置,如果一个INODE page不够,就会继续分配新的INODE page。
page/extent/segment/space
大概知道一些他们的关系,但是貌似还是不是很明白,下面我们来详细的绘制下page extent segment space的关系!
第一个page是FSP HEADER page,存储了space的信息,以及256个XDES Entry,管理接下来的256个extent。
第三个page是INODE page,INODE page中有85个INODE,每个INODE 中有指向分配给该segment的extent的指针。这样INODE就知道了自己对应segment所拥有的extent,也就是page的集合。
介绍完Mysql的文件格式组成,再次强调一下,Mysql存储是用page来存储的。
接下来就是我们最关心的一种page类型了:index page,也就是索引页。
一个Space内大部分都是索引页,也就是我们的数据存储。研究索引页的结构以及存储对理解Mysql的查询优化就显得特别重要。
在Mysql中,索引页分为两种,叶子页(leaf page)/非叶子页(non-leaf page/internal page)。
在非叶子页中,分为中间页和根页(root page),所有的mysql查询都是从root page开始进行B+树的遍历!
Mysql每建立一个索引,就会为其分配两个segment,一个是internal segment,一个是leaf segment。见名思意internal segment存储的是非叶子索引页,leaf segment存储的是叶子索引页。
FIL Header:这个应该不用解释了,每个page都有一个FIL Header。但是与其他page的FIL Header不同的是,每个index page都有一个指向上一个和下一个index page的指针。
FSEG Header:只在root page中有值,它也是有一个小小的结构:
可以看到root page的FSEG Header有两个指针,分别由一个number和offset组成。指向了该索引结构(B+树)的segment。这也呼应了我们之前说的一个索引结构会分配两个segment。
System records:一个索引页包含两个默认的记录,infimum和supremum,以便在遍历时快速定位到索引内记录的位置。
抛开这些琐碎的结构,实际上一个索引页可以表示为如下
最上面是root page,中间是internal page,他们都是non leaf page。只存储
最下面绿色的是leaf page,存放实际的数据。
从root page开始查询,root page包含了一个FSEG Header,通过FSEG Header找到叶子和非叶子索引的segment,然后通过segment,确定到对应extent以及page上,但会定位到page内具体的record。
这里引用一张图来描述这种关系(图片来自Jeremy Cole,MySQL资深工程师):
本文我们认识了Mysql的磁盘文件的结构,以及索引是如何在Mysql中存储的,从全局的角度看Mysql的存储结构,使读者对Mysql存储结构有一个宏观的感知,如果大家感兴趣的话,细节后续我会继续写。希望这篇文章对你能有帮助。
后续:下篇文章我们就通过一些工具,真实的打印出来Mysql的磁盘文件结构!