从根上理解 mysql -数据在表中是怎么存储的
之前说了,一行数据是怎么存储的 以及数据在页中是怎么存储了,现在来看看,数据在表中的是怎么存储的
从根上理解 mysql -一行数据是怎么存储的
从根上理解 mysql -数据在页中是怎么存储的
区
在页的数据结构中 FIL_PAGE_PREV 、FIL_PAGE_NEXT 表示上一个和下一个页号,形成双向链表结构。但是,如果页面是随机分配的话,查询下一页数据的时候就会变成随机IO,随机IO效率是很低的。所以,有了区的概念。
区(extent):连续64个页组成一个区 1MB
组:每256个区划分为一个组,每个组的前几个页面都是固定的(这里很重要),用于存储组和表的通用信息。
先想想为什么要有组的概念???
每个组的前几个页面都是固定的
ok,引入区的概念是为了转随机IO为顺序IO,但是B+树中真正存储数据的是叶子结点,如果把所有节点都放在一起,那么在进行范围查询的时候,也是随机IO。所以,有了段的概念
段
叶子节点和非叶子节点的是分开存放的。存放叶子节点的区的集合就算是一个段,非叶子节点的区集合算另外一个段。即,一个索引会有两个段,一个叶子节点段,一个非叶子节点段。
问题又来了?
一个区默认1M, 一个索引两个段,哪怕一个段中只有一个区,一个索引也是至少2M空间,这未免有点浪费吧。所以有个碎片区的概念 (fragment)
所以,区按照所属,又分为
- Free 空闲的区
- Free_Frag 有剩余空间的碎片区
- Full_Frag 没有剩余空间的碎片区
- Fseg 附属某个段的区
即,一开始为某个段分配页的时候,从碎片化区中分配,当段中已经占据32个零散的页后,就直接申请区进行存储。
上面 Free、Free_Frag、Full_Frag 属于表空间,且每种属性都对应链表。
为了区分区属于哪个段,段中的区也有对应的链表管理
- FREE链表 同一个段中完全空闲的区
- NOT_FULL链表 同一个段中仍有空闲的区
- FULL链表 同一个段中没有空闲的区
所以,每一个段都会对应三个上述链表。即一个索引2个段,每个段3个列表。
上面所说的链表是怎么维护的???
实际上,每个区都会维护一个数据结构:XDES Entry
- Segment ID 该区所属的段Id,当然只针对Fseg,表直属的区该字段无意义
- List Node 主要靠这个将XDES Entry 链接成链表
- State 区的类型 (上面区分的 - Free、Free_Frag、Full_Frag、Fseg )
- Page State Bitmap 16Kb 即128B。128/2 =64 正好对应一个区的64个页,用于表示区中的页是否空闲。
所以,综上:
- 为了避免页的随机访问,有个区的概念(64个连续的页)
- 区分为:a. 表空间的直属区 b.所属段的区
- 每个索引都对应两个段(叶子节点和非叶子节点)
- 表空间的直属的三个区对应三个链表,所属段的区中也对应三个链表,链表是通过XDES Entry数据结构连接的
- 段:碎片区中的32个离散的页+若干个区
- 统计一个表中共有几个链表:3+ n_index * 2 * 3。例:若一个表中有两个索引,那么一共对应15个链表(表空间的3个链表+ 2(索引个数)*2(每个索引段的个数)*3(每个段链表的个数))
段的结构(段是逻辑概念 非物理概念)
ok,消化完,继续。
消化完,问题又来了
- 每个区对应的XDES Entry在哪里
- 直属表空间的 三个链表在哪里(Free、Free_Frag、Full_Frag)
- 每个段对应的INODE Entry存储在哪里
所以,六丁六甲从不吃素…
在上面说到组的时候(256个区定义为一个组)说到,每个组的前几个页面的类型都是固定的,往上翻翻吧。
第一组的第一页 FSP_HDR类型
File Header和File Trailer 是所有类型页面通用的,不再强调了。另外的几个部分中,Empty Space是尚未使用的空间,不用管。重点来看看File Space Header和XDES Entry这两个部分。
File Space Header部分
- Space ID 表空间
- Not Used 未使用
- Size 表的页面数
- List Base Node for FREE List、List Base Node for FREE_FRAG List、List Base Node for FULL_FRAG List 表空间的直属三个列表的基节点。so,表级别的区链表在第一组的第一个页面中
- FRAG_N_USED 这个字段表明在FREE_FRAG链表中已经使用的页面数量。
- FREE Limit 表示在这之前的页面的区均已经被初始化了,可以用了。后面的均未被初始化
- Next Unused Segment ID 下一个段的ID 用户给新建的段 分配id
- Space Flags 其他的布尔类型的属性
- List Base Node for SEG_INODES_FULL List和List Base Node for SEG_INODES_FREE List
XDES Entry部分
每个区都对应一个XDES Entry结构,占40b,但是一个页面就16kb。所以一个页面能放的XDES Entry是有限的,所以,才有了组的概念(不分组 放不下啊)。
第一组的第二页 IBUF_BITMAP类型
记录了一些有关Change Buffer的东西。先不论述,我怕乱了。
第一组的第三页 INODE类型
一个索引对应两个段,还有一些特殊含义的别的段。每个段对应INODE Entry结构。INODE类型的页面就是管理段的。
同理,代表段的INODE Entry结构占用192个字节,一页能存储85个这样的结构,但是如果表中的数据超过85个段,就需要用别的INODE类型的页面来存储。所以就有了List Node for INODE Page List(存储上一个INODE页面和下一个INODE页面的指针)。
List Node for INODE Page List 有两种类型:
- SEG_INODES_FULL链表:该链表中的INODE类型的页面中已经没有空闲空间来存储额外的INODE Entry结构了。
- SEG_INODES_FREE链表:该链表中的INODE类型的页面中还有空闲空间来存储额外的INODE Entry结构了
看到这里熟悉了吧,这两个链表的头节点就在File Space Header中。
非第一组的第一页 XDES类型
这个就很简单了,基本上和第一组的第一页 FSP_HDR类型类似,只是因为第一组的第一页需要存储表的信息。
行了,今天就到这里吧。