一文搞懂MySQL索引页结构

目录
  • 1.前言
  • 2.索引页结构
    • 2.1FileHeader
    • 2.2PageHeader
    • 2.3UserRecords
    • 2.4Infimum&Supremum
    • 2.5PageDirectory
    • 2.6FileTrailer
  • 3.总结

1. 前言

「页」是InnoDB管理存储空间的基本单位,也是内存和磁盘交互的基本单位。也就是说,哪怕你需要1字节的数据,InnoDB也会读取整个页的数据,下次读取的数据如果恰巧也在这个页里,就能命中缓存了。写也是一样的,写数据前要先把页加载到内存,然后在内存中修改,该页被记为「脏页」,脏页淘汰之前必须刷盘。

InnoDB有很多类型的页,它们的用处也各不相同。比如:有存放undo日志的页、有存放INODE信息的页、有存放Change Buffer信息的页、存放用户记录数据的页等等。今天我们要聊的,就是最基础也是最重要的,存放用户记录数据的「索引页」。

2. 索引页结构

InnoDB默认的页大小是16KB,在初始化表空间之前可以在配置文件中进行配置,一旦初始化完成就不可再变更了。查看页大小的命令如下,显示的是字节数。

SHOW VARIABLES LIKE 'innodb_page_size';

索引页结构如下图所示:

索引页由七部分组成,其中Infimum和Supremum也属于记录,只不过是虚拟记录,这里为了与用户记录区分开,还是决定将两者拆开。

名称 大小 描述
File Header 38字节 所有页的通用文件头信息
Page Header 56字节 索引页特有的页头信息
Infimum+Supremum 26字节 页中虚拟的最小、最大记录
User Records 变长 用户记录数据
Free Space 变长 空闲空间
Page Directory 变长 页目录,加速页内数据检索效率
File Trailer 8字节 所有页的通用文件尾信息,校验页是否完整

2.1 File Header

File Header是所有页都有的一个通用的结构,占用固定的38字节,它记录了页的一些通用的状态信息,例如:页的页号、Checksum、把页串联成双向链表的指针、页的类型等等。

名称 大小 描述
FIL_PAGE_SPACE_OR_CHECKSUM 4字节 新版本中代表页的校验和Checksum
FIL_PAGE_OFFSET 4字节 页号
FIL_PAGE_PREV 4字节 上一个页的页号
FIL_PAGE_NEXT 4字节 下一个页的页号
FIL_PAGE_LSN 8字节 页面最后被修改时的LSN值
FIL_PAGE_TYPE 2字节 页的类型
FIL_PAGE_FILE_FLUSH_LSN 8字节 仅在系统表空间的第1个页中使用,代表文件至少被刷新到了对应的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4字节 页数据哪个表空间

FIL_PAGE_SPACE_OR_CHECKSUM

基于当前页计算出的校验和(Checksum),可以把它看作是哈希值,校验和不同,则两个页数据肯定不同。它的作用是InnoDB在脏页刷盘时,有可能会遇到页刷到一半断电的情况,页的头和尾部分分别记录校验和,只有当头尾的校验和一致的时候,才代表磁盘上的页是完整的,否则就是一个损坏的页。

FIL_PAGE_OFFSET

页号,页的唯一标识,全局递增的数字,InnoDB通过页号来定位唯一的一个页。4字节存储,意味着一个表空间最多可以有232个页,按照一个页16KB计算,则一个表空间最多支持64TB的数据。

FIL_PAGE_PREV & FIL_PAGE_NEXT

一个页大小才16KB,一张表数据其实是由N多个页构成的,页与页之间在物理上可以是不连续的,但是逻辑上要连续,FIL_PAGE_PREV和FIL_PAGE_NEXT分别指向当前页的上一个页和下一个页的页号,通过这两个指针将索引页串联成了一个双向链表。记录与记录之间是单向的,页与页之间是双向的!

FIL_PAGE_LSN

页面最后被修改时,对应的LSN值。LSN的全称是Log Sequence Number,日志序列号。它是一个递增的数字,和事务相关,这里不作赘述。

FIL_PAGE_TYPE

当前页的类型,InnoDB为了不同的目的设计了很多不同类型的页,索引页的固定值是0x45BF

FIL_PAGE_FILE_FLUSH_LSN

仅在第1个页中使用,用来判断数据库是正常关闭还是异常宕机。

FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID

仅记录当前页数据哪个表空间。

2.2 Page Header

Page Header是索引页特有的结构,占用固定的56字节,它记录了索引页中记录相关的状态信息。

名称 大小 描述
PAGE_N_DlR_SLOTS 2字节 页目录中的槽数量
PAGE_HEAP_TOP 2字节 未使用的空间最小地址,User Records和Free Space分界点
PAGE_N_HEAP 2字节 本页中的记录的数量(包括虚拟记录和删除记录)
PAGE_FREE 2字节 第一个删除的记录地址,后续删除的记录会形成链表。
PAGE_GARBAGE 2字节 已删除记录占用的字节数
PAGE_LAST_INSERT 2字节 最后插入记录的位置
PAGE_DIRECTION 2字节 记录插入的方向
PAGE_N_DIRECTION 2字节 同一个方向连续插入的记录数量
PAGE_N_RECS 2字节 该页中记录的数量(不包括虚拟记录和删除记录)
PAGE_MAX_TRX_ID 8字节 修改当前页的最大事务ID,仅在二级索引中使用
PAGE_LEVEL 2字节 当前页在B+树中所处的层级
PAGE_INDEX_ID 8字节 索引ID,表示当前页属于哪个索引
PAGE_BTR_SEG_LEAF 10字节 B+树叶子段的头部信息,仅在B+树的Root页定义
PAGE_BTR_SEG_TOP 10字节 B+树非叶子段的头部信息,仅在B+树的Root页定义

不用每个属性都了解,我们挑几个比较重要的看看。

PAGE_N_DlR_SLOTS

一个页内可能有上千条记录,挨个遍历的话效率太慢了。为了提高页内记录的检索效率,InnoDB将页内的记录划分为多个组,组里最大的那条记录相较于页的地址偏移量会记录到「Page Directory」部分,每个组都对应一个槽,槽的大小是固定的2字节。该属性记录的就是页内槽的数量。

PAGE_HEAP_TOP

Free Space的起始位置,它是User Records和Free Space分界点。一个全新的页一开始是没有User Records部分的,每插入一条记录,都要向Free Space申请空间,Free Space耗尽就代表页满了。

PAGE_FREE

DELETE命令删除记录时,InnoDB并不会真的将记录从磁盘中删除,而是在记录的头信息里打个标记,然后将其加入到「垃圾链表」中。PAGE_FREE指向的就是垃圾链表的表头记录。后面删除的记录,也会自动加入到链表里。

PAGE_DIRECTION & PAGE_N_DIRECTION

PAGE_DIRECTION表示最后一条记录插入的方向,比上一条记录值大则记为右边,反之则是左边。PAGE_N_DIRECTION表示同一方向连续插入的记录数,方向变了该值就会重置。

PAGE_LEVEL

InnoDB组织数据的形式就是B+树,树中的节点就是索引页,PAGE_LEVEL代表当前页在B+树中所处的层级。InnoDB规定,叶子节点层级为0,然后向上递增。

2.3 User Records

Infimum和Supremum也属于记录,只是为了与用户记录区分开才划分成了两部分,我们先看User Records。

用户记录存放在User Records部分,一个全新的页一开始全是Free Space,是没有User Records部分的。每插入一条记录都需要到Free Space申请一块空间,并将其划分到User Records用来存放用户记录。当Free Space耗尽也就代表当前页已经用完了,再有新记录需要插入,就需要申请一个新的页了。

还记得MySQL的行格式吗?它决定了记录在磁盘里的存储格式。以COMPACT为例,存储格式如下图:

记录头信息里的字段比较关键,以防大家忘记,我这里再贴一下:

名称 大小(Bit) 说明
预留位1 1 没有使用
预留位2 1 没有使用
deleted_flag 1 记录删除标记
min_rec_flag 1 B+树非叶子节点的最小目录项标记
n_owned 4 同一页内同一组里最大的记录会记录组里的记录数量,其余记录该值为0
heap_no 13 当前记录在页面堆里的相对位置
record_type 3 记录类型。0:普通记录,1:B+树非叶子节点目录项记录,2:Infimum记录,3:Supremum记录.
next_record 16 下一条记录的相对位置

记录头信息的最后2字节用来连接下一条记录,将页内所有记录串联成一个单向链表。所以我们隐藏变长字段长度列表和NULL值列表,记录的格式应该是这样的:

记录是怎么排序的?
我们已经知道,页内的记录会自动串联成一个单向链表。那这个链表的编排顺序是什么呢?是按照记录的插入时间排序的吗?其实不是的,如果表有主键,会根据主键排序;没主键有唯一非空索引,会根据该索引排序;两者都没有,InnoDB会自动生成一个row_id列并根据该列进行排序。

若无特殊说明,本文均假定表有主键。

2.4 Infimum & Supremum

Infimum和Supremum是索引页内的两条虚拟记录,InnoDB规定所有索引页都会有这两条记录,而且所有的用户记录都比Infimum大,都比Supremum小。
记录头信息里的heap_no代表记录在堆里的相对位置,该值越小代表记录越靠前。细心的同学会发现,上图中的用户记录heap_no值是从2开始的,那0和1呢?不说你也肯定猜到了,就是被Infimum和Supremum占用了。Infimum和Supremum的heap_no值分别是0和1,它俩在所有用户记录的最前面。

Infimum和Supremum结构非常的简单,和用户记录一样也有头信息,真实数据部分是固定的字符串,如下图所示:

我们把这两条虚拟记录也加入到记录里面,完整的结构就是下面这样的:

Supremum记录的next_record属性为0,代表它已经没有下一条记录了。

2.5 Page Directory

Free Space没什么好说的,就是一块未被使用的空闲空间。

Page Directory也叫作「页目录」,它的目的是提高页内记录的检索效率。相较于一张表几千万的记录来说,一个页内几百上千条记录已经是很少很少了。可即便如此,它也有几百上千条啊,如果页内检索记录只能挨个遍历的话,那也太低效了。别忘了,页内的记录是根据索引值排好序的,我们可以巧用「二分法」来快速查找。

具体做法是:将页内所有非删除的记录划分为N个组,每个组里最后一条记录(即主键最大的记录)称作“大哥”,其余记录是“小弟”,“大哥”的n_owned属性记录了组内的记录数量。将“大哥”在页内的地址偏移量提取出来,按顺序依次从File Trailer部分往前写,每个地址偏移量占用2字节,称作一个「槽」,Page Directory就是由这些槽构成的。
InnoDB对于分组内的记录数量有一些规定:

  • Infimum记录所在分组,只能有一条记录。
  • Supremum记录所在分组,允许有1~8条记录。
  • 其余分组,允许有4~8条记录。

由此可见,一个组里最多有8条记录,只要通过二分法快速定位到组,InnoDB也只需要遍历这8条记录,相较于遍历页内所有记录,效率要高的多。

2.6 File Trailer

File Trailer是所有页都有的通用结构,占用固定的8字节,它的主要作用就是为了校验页的完整性。磁盘的速度实在是太慢了,InnoDB不会每次写点数据都直接刷新到磁盘上,那样MySQL会慢死。而是将页作为刷盘的基本单位,数据修改时,先改内存里的页,稍后再将整个页的数据一次性刷新到磁盘里。但是这会带来一个问题,一个页16KB,刷到第10KB的时候磁盘断电了怎么办?重启后InnoDB如何判断磁盘里的页数据是完整的?

InnoDB是这么处理的,刷盘前根据页数据计算出一个Checksum,在页头和页尾都写一份。页刷盘的时候,先刷页头再刷页尾,当头尾两个Checksum值一致的时候,代表磁盘里的页是完整的,否则就表示页头刷了页尾没刷,那肯定是刷到一半出错了。

大小 说明
4字节 页的校验和Checksum
4字节 页最后被修改时对应的LSN的后4个字节,正常情况下应该与File Header里的FIL_PAGE_LSN的后4个字节相同。

3. 总结

页是InnoDB存取数据的基本单位,默认页大小是16KB,InnoDB为了不同的目的设计了很多不同类型的页,本文重点分析了存放用户记录的索引页。页的头尾部分File Header和File Trailer是所有页都有的一个通用结构,它们记录了页的一些通用状态信息,和Checksum用来验证页的完整性。Page Header是索引页特有的结构,它记录了页内用户记录相关的状态信息。User Records部分用来存放用户记录。另外,由于页内的记录数量也不少,为了提高页内记录的检索效率,InnoDB在索引页中加入了Page Directory,它通过将记录分组,将组里最大的记录的地址偏移量形成一个个槽,Page Directory就是由这些槽构成的。检索数据时,使用二分法快速定位到槽所在的组,就可以避免遍历所有组的记录了。

到此这篇关于MySQL索引页结构的文章就介绍到这了,更多相关MySQL索引页结构内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mysql聚簇索引的页分裂原理实例分析

    本文实例讲述了mysql聚簇索引的页分裂.分享给大家供大家参考,具体如下: 在MySQL中,MyISAM采用的是非聚簇索引的,InnoDB存储引擎是采用聚簇索引的. 聚簇结构的特点: 根据主键查询条目时,不用回行(数据就在主键节点下) 如果碰到不规则数据插入时,造成频繁的页分裂 为什么会产生页分裂? 这是因为聚簇索引采用的是平衡二叉树算法,而且每个节点都保存了该主键所对应行的数据,假设插入数据的主键是自增长的,那么根据二叉树算法会很快的把该数据添加到某个节点下,而其他的节点不用动:但是如果插入的

  • MySQL索引优化之分页探索详细介绍

    目录 ​​MySQL​​索引优化之分页探索 案例一 案例二 ​​MySQL​​索引优化之分页探索 表结构 CREATE TABLE `demo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '姓名', `age` int(11) NOT NULL DEFAULT '0'

  • Mysql limit 优化,百万至千万级快速分页 复合索引的引用并应用于轻量级框架

    MySql 这个数据库绝对是适合dba级的高手去玩的,一般做一点1万篇新闻的小型系统怎么写都可以,用xx框架可以实现快速开发.可是数据量到了10万,百万至千万,他的性能还能那么高吗?一点小小的失误,可能造成整个系统的改写,甚至更本系统无法正常运行!好了,不那么多废话了.用事实说话,看例子: 数据表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 是逐渐,vtype是tinyint,vtype是索引.这是一个

  • Oracle与Mysql主键、索引及分页的区别小结

    区别: 1.主键,Oracle不可以实现自增,mysql可以实现自增. oracle新建序列,SEQ_USER_Id.nextval 2.索引: mysql索引从0开始,Oracle从1开始. 3.分页, mysql: select * from user order by desc limit n ,m. 表示,从第n条数据开始查找,一共查找m条数据. Oracle:select * from user select rownum a * from ((select * from user)a

  • 一文搞懂MySQL索引页结构

    目录 1.前言 2.索引页结构 2.1FileHeader 2.2PageHeader 2.3UserRecords 2.4Infimum&Supremum 2.5PageDirectory 2.6FileTrailer 3.总结 1. 前言 「页」是InnoDB管理存储空间的基本单位,也是内存和磁盘交互的基本单位.也就是说,哪怕你需要1字节的数据,InnoDB也会读取整个页的数据,下次读取的数据如果恰巧也在这个页里,就能命中缓存了.写也是一样的,写数据前要先把页加载到内存,然后在内存中修改,该

  • 一文搞懂MySQL持久化和回滚的原理

    目录 redo log 为什么要先更新内存数据,不直接更新磁盘数据? 为什么需要redo log? redo log是如何实现的? 为什么一个block设计成512字节? 为什么要两段式提交? crash后是如何恢复的? undo log 什么情况下会生成undo log? undo log是如何回滚的? undo log存在什么地方? redo log 事务的支持是数据库区分文件系统的重要特征之一,事务的四大特性: 原子性:所有的操作要么都做,要么都不做,不可分割. 一致性:数据库从一种状态变

  • 一文搞懂Mysql中的共享锁、排他锁、悲观锁、乐观锁及使用场景

    目录 一.常见锁类型 二.Mysql引擎介绍 三.常用引擎间的区别 四.共享锁与排他锁 五.排他锁的实际应用 六.共享锁的实际应用 七.死锁的发生 八.另一种发生死锁的情景 九.死锁的解决方式 十.意向锁和计划锁 十一.乐观锁和悲观锁 总结 一.常见锁类型 表级锁,锁定整张表 页级锁,锁定一页 行级锁,锁定一行 共享锁,也叫S锁,在MyISAM中也叫读锁 排他锁,也叫X锁,在MyISAM中也叫写锁 悲观锁,抽象性质,其实不真实存在 乐观锁,抽象性质,其实不真实存在 常见锁类型 二.Mysql引擎

  • 一文搞懂MySQL运行机制原理

    目录 前言 MySQL服务器体系架构 网络连接层 服务层 存储引擎层 系统文件层 服务器处理客户端请求 连接管理 解析与优化 查询缓存 语法解析 查询优化 存储引擎 小结 前言 前文我们了解了MySQL采用客户端/服务器架构,用户通过客户端程序发送增删改查需求,服务器程序收到请求后处理,并且把处理结果返回给客户端.这篇文章主要看下MySQL服务端是如何处理客户端的请求,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助. MySQL服务器体系架构 MySQL Server架构自顶向下大致可以分网

  • 一文搞懂MySQL脏读,幻读和不可重复读

    目录 MySQL 中事务的隔离 1.READ UNCOMMITTED 2.READ COMMITTED 3.REPEATABLE READ 4.SERIALIZABLE 前置知识 1.事务相关的常用命令 2.MySQL 8 之前查询事务的隔离级别 3.MySQL 8 之后查询事务的隔离级别 4.查看连接的客户端详情 5.查询连接客户端的数量 6.设置客户端的事务隔离级别 7.新建数据库和测试数据 8.名称约定 脏读 1.脏读演示步骤1 2.脏读演示步骤2 3.脏读演示步骤3 4.不可重复读 5.

  • 一文搞懂MySQL预编译

    1.预编译的好处 大家平时都使用过JDBC中的PreparedStatement接口,它有预编译功能.什么是预编译功能呢?它有什么好处呢? 当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句.其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多. 如果我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时

  • 一文搞懂MySQL XA如何实现分布式事务

    目录 前言 XA 协议 如何通过MySQL XA实现分布式事务 前言 MySQL支持单机事务的良好表现毋庸置疑,那么在分布式系统中,涉及多个节点,MySQL又是如何实现分布式事务的呢?比如开发一个业务系统,它接受外部的请求,然后访问多个内部其它系统才能执行该请求.执行时我们需要同时更新多个数据库的值(D1,D2,D3).由于系统必须处于一个一致性,也就是这三个数据库的值要么同时更新成功,要么全部不更新.不然会造成子系统有些指令成功了,有些指令尚未执行.导致对结果理解混乱. 那么,MySQL如何实

  • 一文搞懂MySQL元数据锁(MDL)

    目录 一.什么是metadata lock 二.MDL和行锁有什么区别 三.MDL为什么会造成系统崩溃 四.MDL的生命周期有多长 五.如何快速找到阻塞源头 六.本文开始的案例最终如何解决 小结 某日,路上收到用户咨询,为了清除空间,想删除某200多G大表数据,且已经确认此表不再有业务访问,于是执行了一条命令‘delete from bigtable’,但好长时间也没删完,经过咨询后,获知drop table删除表速度快,而且能彻底释放空间,于是又在另外一个session中执行了‘drop ta

  • 一文弄懂MySQL索引创建原则

    目录 一.适合创建索引 1.字段的数值有唯一性限制 2.频繁作为Where查询条件的字段 3.经常Groupby和Orderby的列 4.Update.Delete的where条件列 5.Distinct字段需要创建索引 6.多表Join连接操作时,创建索引注意事项 7.使用列的类型小的创建索引 8.使用字符串前缀创建索引 9.区分度高的列适合作为索引 10.使用最频繁的列放到联合索引的左侧 11.在多个字段都要创建索引的情况下,联合索引由于单值索引 二.不适合创建索引 1.在where中使用不

  • 五分钟带你搞懂MySQL索引下推

    目录 什么是索引下推 索引下推优化的原理 索引下推的具体实践 没有使用ICP 使用ICP 索引下推使用条件 相关系统参数 总结 如果你在面试中,听到MySQL5.6"."索引优化" 之类的词语,你就要立马get到,这个问的是"索引下推". 什么是索引下推 索引下推(Index Condition Pushdown,简称ICP),是MySQL5.6版本的新特性,它能减少回表查询次数,提高查询效率. 索引下推优化的原理 我们先简单了解一下MySQL大概的架构:

随机推荐