共计 2897 个字符,预计需要花费 8 分钟才能阅读完成。
最新版本请查看原文:https://blog.haohtml.com/arch…
在介绍 InnoDB 中的页的时候,很有必要先让大家理解一下 InnoDB 中的存储构造
从 InnoDB 存储引擎的逻辑构造看,所有数据都被逻辑地寄存在一个空间内,称为表空间(tablespace),而表空间由段(sengment)、区(extent)、页(page)组成。在一些文档中 extend 又称块(block)。
一、表空间(table space)
表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中能够有一个或多个段,然而一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从治理上能够划分为零碎表空间、用户表空间、撤销表空间、长期表空间等。
在 InnoDB 中存在两种表空间的类型:共享表空间和独立表空间。如果是共享表空间就意味着多张表共用一个表空间。如果是独立表空间,就意味着每张表有一个独立的表空间,也就是数据和索引信息都会保留在本人的表空间中。独立的表空间能够在不同的数据库之间进行迁徙。可通过命令
mysql > show variables like 'innodb_file_per_table';
查看以后零碎启用的表空间类型。目前最新版本曾经默认启用独立表空间。
InnoDB 把数据保留在表空间内,表空间能够看作是 InnoDB 存储引擎逻辑构造的最高层。实质上是一个由一个或多个磁盘文件组成的虚构文件系统。InnoDB 用表空间并不只是存储表和索引,还保留了回滚段、双写缓冲区等。
二、段(segment)
段(Segment)由一个或多个区组成,区在文件系统是一个 间断调配 的空间(在 InnoDB 中是间断的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的调配单位,不同类型的数据库对象以不同的段模式存在。当咱们创立数据表、索引的时候,就会相应创立对应的段,比方创立一张表时会创立一个表段,创立一个索引时会创立一个索引段。
三、区(extent)
在 InnoDB 存储引擎中,一个区会调配 64 个间断的页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64*16KB=1MB。在任何状况下每个区大小都为 1MB,为了保障页的连续性,InnoDB 存储引擎每次从磁盘一次申请 4 - 5 个区。默认状况下,InnoDB 存储引擎的页大小为 16KB,即一个区中有 64 个间断的页。
四、页(Page)
页是 InnoDB 存储引擎磁盘治理的最小单位,每个页默认 16KB;InnoDB 存储引擎从 1.2.x 版本碍事,能够通过参数 innodb_page_size 将页的大小设置为 4K、8K、16K。若设置实现,则所有表中页的大小都为 innodb_page_size,不能够再次对其进行批改,除非通过 mysqldump 导入和导出操作来产生新的库。
innoDB 存储引擎中,常见的页类型有:
1. 数据页(B-tree Node)
2. undo 页(undo Log Page)
3. 零碎页(System Page)
4. 事物数据页(Transaction System Page)
5. 插入缓冲位图页(Insert Buffer Bitmap)
6. 插入缓冲闲暇列表页(Insert Buffer Free List)
7. 未压缩的二进制大对象页(Uncompressed BLOB Page)
8. 压缩的二进制大对象页(compressed BLOB Page)
五、行(row)
InnoDB 存储引擎是按行进行寄存的,每个页寄存的行记录也是有硬性定义的,最多容许寄存 16KB/2-200,即 7992 行记录。
理解了整体架构,上面咱们开始具体对 Page 来做一些介绍。
先贴一张 Page 残缺的结构图
上较的概念切实太多了,为了不便了解,能够按上面的合成一下 Page 的构造
每局部的意义
页构造整体上能够分为三大部分,别离为通用局部(文件头、文件尾)、存储记录空间、索引局部。
第一局部通用局部,次要指文件头和文件尾,将页的内容进行封装,通过文件头和文件尾校验的 CheckSum 形式来确保页的传输是残缺的。
在文件头中有两个字段,别离是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT,它们的作用相当于指针,别离指向上一个数据页和下一个数据页。连接起来的页相当于一个双向的链表,如下图所示:
须要阐明的是采纳链表的构造让数据页之间不须要是物理上的间断,而是逻辑上的间断。
第二个局部是记录局部,页的次要作用是存储记录,所以“最小和最大记录”和“用户记录”局部占了页构造的次要空间。另外闲暇空间是个灵便的局部,当有新的记录插入时,会从闲暇空间中进行调配用于存储新记录,如下图所示:
一个页内必须存储 2 行记录,否则就不是 B +tree,而是链表了。
第三局部是索引局部 ,这部分重点指的是页目录(示意图 2 中的 s0-sn),它起到了记录的索引作用,因为在页中,记录是以 单向链表 的模式进行存储的。单向链表的特点就是插入、删除十分不便,然而检索效率不高,最差的状况下须要遍历链表上的所有节点能力实现检索,因而在页目录中提供了二分查找的形式,用来进步记录的检索效率。这个过程就好比是给记录创立了一个目录:
将所有的记录分成几个组,这些记录包含最小记录和最大记录,但不包含标记为“已删除”的记录。
第 1 组,也就是最小记录所在的分组 只有 1 个记录 ;
最初一组,就是最大记录所在的分组,会有 1-8 条记录;
其余的组记录数量在 4-8 条之间。
这样做的益处是,除了第 1 组(最小记录所在组)以外,其余组的记录数会尽量平分。
在每个组中最初一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段。
页目录用来存储每组最初一条记录的地址偏移量,这些地址偏移量会依照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最初一个记录。如下图所示:
页目录存储的是槽,槽相当于分组记录的索引。咱们通过槽查找记录,实际上就是在做二分查找。这里我以下面的图示进行举例,5 个槽的编号别离为 0,1,2,3,4,我想查找主键为 9 的用户记录,咱们初始化查找的槽的上限编号,设置为 low=0,而后设置查找的槽的下限编号 high=4,而后采纳二分查找法进行查找。
首先找到槽的两头地位 p=(low+high)/2=(0+4)/2=2,这时咱们取编号为 2 的槽对应的分组记录中最大的记录,取出关键字为 8。因为 9 大于 8,所以应该会在槽编号为 (p,high] 的范畴进行查找
接着从新计算两头地位 p’=(p+high)/2=(2+4)/2=3,咱们查找编号为 3 的槽对应的分组记录中最大的记录,取出关键字为 12。因为 9 小于 12,所以应该在槽 3 中进行查找。
遍历槽 3 中的所有记录,找到关键字为 9 的记录,取出该条记录的信息即为咱们想要查找的内容。
B+ 树是如何进行记录检索的?
如果通过 B+ 树的索引查问行记录,首先是从 B+ 树的根开始,逐层检索,直到找到 叶子节点 ,也就是找到对应的 数据页 为止,将数据页加载到内存中,页目录中的槽(slot)采纳二分查找的形式先找到一个粗略的 记录分组 ,而后再在分组中通过链表遍历的形式查找 记录。