MySQL InnoDB数据页结构,数据在单页内是如何存储的,如何快速定位数据位置

InnoDB为不同的目的设计了很多不同类型的页:

  • 存放空间头部信息的页
  • 存放ChangeBuffer的页
  • 存放INODE信息的页
  • 存档UNDO日志信息的页
  • 存档记录的页(索引页)
  • 等等

目录

1 页结构

1.1 User Records

1.1.1 User Records 中的记录头信息

1.2 Page Directory

那么,划分组的依据是什么呢?

那么,想要查找一条记录时,是怎样提高效率的呢?

那么,如何找到槽2所在分组中主键最小的那条记录呢?

1.3 页面头部

1.4 File Header

1.5 File Trailer


1 页结构

1.1 User Records

        InnoDB会将我们自己的存储记录会按照指定的行格式存储到 User Records 部分,一开始 User Records 是空的,我们每次插入记录,都会向 Free Space 申请空间。当一个页面的 Free Space 用完的时候,也就意味着页空间用完了,以后再想插入数据,就需要重新申请新的页了。

1.1.1 User Records 中的记录头信息

        上面提到,InnoDB会将存储记录会按照指定的行格式存储到 User Records 部分,这个行格式有几种:COMPACT、REBUNDANT、DYNAMIC、COMPRESSED。具体自行查阅资料。

        其中 COMPACT 行格式包含一个数据列叫做头信息,共5个字节,对应位描述如下图(从前到后顺序排序):

 

  • delete_mask

        用于记录该条记录是否被删除,InnoDB中,如果你删除了一条数据,比如执行了一条命令

DELETE FROM 表 WHERE 条件

        该条数据并不会在页中真正被删除,它还存在真实磁盘上,InnoDB只会把删除标志位标记为1,因为如果直接删除该行数据,其他数据需要重新移位,这意味着性能开销。被删除的数据会组成一个垃圾链表,这个链表占用的空间是可重用空间,当有新记录插入,这部分空间可能会被重用,即辣鸡数据被覆盖。

  • heap_no

        InnoDB将记录一条一条紧挨着排列,这种结构叫做堆(heap),在堆中的相对位置叫heap_no。heap_no从2开始,0是Infimum,表示页面中最小记录,1是Supremum表示页面中最大记录,即页面中用户自己的纪录区间为 [2,+∞)。请注意,每插入一条记录,heap_no的值+1,但这不是主键值!这只是heap_no的值。之所以说Infimum、Supremum是最小(大)记录,只是因为就是这么规定的(这是规定!没有为什么,人家就是这么设计的!),并不是说它们的主键最大!各个记录之间通过主键值比较大小,而不是heap_no比较大小。

  • record_type

        表示记录的类型,0是普通用户记录,1是B+树中的非叶子节点的目录项记录,2表示Infimum,3表示Supremum。

  • next_record

        请背诵,请背诵,请背诵,这个属性超级重要。表示从当前记录的真实数据到下一条记录的真实数据的距离。正数表示下一条记录在当前记录的后边,负数表示在前边,也就是说,这其实就是个链表。请注意,下一条记录指的是主键由小到大排序的下一条记录,而不是插入顺序!!

        当你删除链表中一条数据,那么会它的删除标志位标志为1,它的next_record标志为0,说明没有下一个元素了。而原本指向它的next_record会只想它原本指向的元素。

        也就是说,无论怎么操作页中的记录,请记住,是本页中,这里没有讨论跨页的问题。InnoDB会始终维护一个单向链表,这个单向链表的第零个元素是Infimum,最后一个元素是Supremum,中间的元素按照主键从小到大的顺序链接。

  • n_owned

        InnoDB会将页数据分成几组,具体怎么分可以自己查资料,每组最后一个元素是大哥,负责管理组内的小弟(包括他自己),只有大哥这个属性的值不为0,其余为0,表示该组的记录个数。至于为什么分组,这里大概说一下,比如你有100页书,你分为1~25,26~50,51~75,76~100,共4组,其中(25,50,75,100)是大哥,假如你要找88在哪,没分组前你得找多少次?分组后你需要找多少次?相信大家都能算明白,这个属性正是为了提高查找效率而生。

1.2 Page Directory

        现在,我们知道了页内的数据通过链表,按照主键从小到大链接起来。也知道了n_owned,即InnoDB为提高查找效率,会将页内数据进行分组。那么选出大哥后,存储在哪呢?答案是 Page Directory(页目录)中。InnoDB 分组后,会将大哥所在页面中的地址偏移量(页面中第0个字节~真实数据之间的距离)提取出来,存储到页目录中,每个地址偏移量称作槽(Slot),每个槽占用2个字节(因为2个字节最大可以表示65536位,而一页16K,所以2字节就够了),页目录由这些槽组成。请注意,槽存储的是地址偏移量,地址偏移量,地址偏移量而不是主键!!

那么,划分组的依据是什么呢?

        (1)初始情况下只有 Infimum 和 Supremum两条记录,它俩各自成一组,规定Infimum 组只有一条记录就是它自己,Supremum可以管理1~8条记录,其余分组条数是4~8条记录。

        (2)每插入一条记录,找到页目录中对应记录的主键值比待插入记录的主键值大,并且差值最小的槽,然后把槽管理记录 n_owned +1,表示本组添加了一条记录,直到该组记录数等于8

        (3)当组记录数等于8后,再插入一条记录,这时候装不下了怎么办?答案是把小组裂开,分成2个组,其中一个组4个记录,一个组5个记录。

那么,想要查找一条记录时,是怎样提高效率的呢?

        由于槽之间是紧挨着顺序排列的, 所以可以使用二分法查找。(二分法yyds)比如又5个槽,编号为0(Infimum)、1(索引值为4)、2(索引值为8)、3(索引值为12)、4(..Supremum)。假设要插入索引值为6的行,那么根据二分法(0+4)/2=2,找到槽2,槽记录了地址偏移量,所以能够快速定位到索引值为8,继续二分(0+2)/2=1,找到槽1对应的索引值为4,最后确认插入槽2,找到槽2所在分组中主键最小的那条记录,然后沿着单向链表遍历,找到插入位置,你不必担心组内遍历的效率,因为最大也就8条记录,慢不了多少。

那么,如何找到槽2所在分组中主键最小的那条记录呢?

        别忘了,槽是顺序排列的,而槽是分组中索引值最大的行的地址,所以到槽1拿到地址偏移量,再取next_record就是槽2所在分组中主键最小的那条记录。

 

1.3 页面头部

1.4 File Header

 

  • FILE_PAGE_PREV和FILE_PAGE_NEXT

        用来形成双向链表,分别链上一页和下一页。

1.5 File Trailer

        用来检测一页是否完整。该部分由8字节组成:

  •         前4个字节用于存储校验和:防止页刷新到磁盘,刷了一半突然宕机咋办?正常情况下 File Header 的 FIL_PAGE_SPACE_OR_CHKSUM应该和 File Trailer保持一致,如果不统一就意味着刷新期间发生了错误。
  •         后4个字节用于存储最后修改页面时对应的LSN的后4个字节,正常情况和File Header 的FIL_PAGE_LSN一致。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值