MySQL --- 读书笔记 --- InnoDB数据结构

数据存储结构:页

磁盘和内存交互的基本单位:页

  • 页的默认大小是16KB

  • 基本交互单位是页,也就是每次最少从磁盘加载16KB到内存,每次最少将内存中的16KB刷新到磁盘。

  • 在数据库中,不论是读一行还是读多行,都是将这些行所在的页进行加载,也就是说,数据库管理存储空间的基本单位是页,数据库I/O操作的最小单位是页

  • 页之间的存储不是物理上的连续存储,它们之间是通过双向链表关联

  • 每个数据页中的记录会按照主键值顺序组成一个单向链表

  • 每个数据页都会为存储在其中的记录生成一个页目录

页的上层结构

  • 一个区会分配64个页,所以一个区是1MB
  • 一个段由一个或多个区组成,区在文件系统中是一个连续分配的空间,但是在段中不要求区是连续的;段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。比如说创建数据库或者索引的时候,会分配不同的段
  • 表空间是一个逻辑容器,在表空间中可以有一个或多个段,但是一个段只能属于一个表空间,数据库由一个或多个表空间组成

页的内部结构

  • 数据页、系统页、Undo页、事务数据页

  • 文件头(38bytes)
  • 页头(56bytes)
  • 最大最小记录(26bytes)
  • 用户记录(不确定)
  • 空闲空间(不确定)
  • 页目录(不确定)
  • 文件尾(8bytes)

文件头部(38bytes)
  • 描述页的通用信息,比如页的编号、上一页、下一页

  • 页号,唯一定位
  • 页类型,索引页、Undo页
  • 上一页、下一页,逻辑上的连续
  • 校验和,校验页的完整性,与文件尾的校验和对比,防止因同步失败造成的数据不完整
  • 页面被最后修改时对应的日志序列的位置
用户记录
  • 我们存储的记录会按照指定的行格式存储到UserRecords部分,一开始并没有UserRecords部分,每当插入一条记录,就会从FreeSpace部分申请一个记录大小的空间划分到UserRecords部分,当FreeSpace部分全部被UserRecords代替,这个页就满了
页目录
  • 为什么需要页目录:记录在页中是以单向链表的形式存储的,它的特点是插入删除快,但是检索效率不高,所以专门在页中增加一个页目录,可以通过二分查找算法来快速检索记录
  • 具体页目录并不是完全复制一份记录的主键,那样体积会太大,所以页目录又将所有记录分成若干个组,只记录每个组的最大记录,但是其中不包含已删除的记录
  • 每个组的数量并不是平分,第一组只放了最小记录,最后一组会有包含最大记录在内1-8条记录,其余的组会有4-8条记录
  • 页目录中存储的是每个组最后一条记录的地址偏移量,这个被称为slot(槽)

  • 如何使用页目录查找记录:假设已经找到了记录所在的叶子节点,那么最后,需要在这个页中快速查找出这条记录;使用二分法对页目录的slot进行比较查找,这时候有可能只能查找出记录是在哪个组中,由于slot是指向组中最大记录,所以我们需要这个组的上一个组的slot,然后我们就可以不断获取next_record来查找记录
页头
  • 专门用于在这个字段记录,页的诸多状态信息,比如页的第一条记录、页中有多少个slot、页中有多少条记录

行格式

  • 记录在磁盘中的存放方式叫做行格式
    1. Compact
    2. Redundant
    3. Dynamic (MySQL8.0默认)
    4. compressed

Compact



变长字段长度列表

  • MySQL中支持一些变长字段,比如varchar、text等类型字段,那么变长字段的长度是不确定的,所以在存储真实数据的时候,需要把数据的长度也保存起来,那么在Compact行格式中,会把所有的变长字段的真实占用字节长度都保存在记录的变长字段长度列表字段中

NULL值列表

  • Compact会把所有可以存储为NULL的列统一管理起来,如果没有可以存为NULL值的列,那么NULL值列表也就不存在了
  • 那么为什么需要标记出可以是NULL值的字段呢,因为数据需要对齐,如果没有标注出来NULL值的位置,就有可能在查询的时候出现混乱;如果使用特殊符号放在相应数据位置,虽然结果可以,但是这样会浪费空间

  • 二进制表示,NULL值为1,非NULL为0,这样在读列数据时,就不需要存特殊符也能区分NULL值
  • 主键会被跳过,因为主键必然非NULL

记录头信息

  • delete_mask: 被删除不立即删除,是因为移除它们之后其他记录需要在磁盘上重新排序,导致性能消耗;所以只是打一个标签,所有的被删除记录组成一个垃圾链表,在这个链表中的记录占用的空间叫做可重用空间,之后有新加入的记录,可能把这些被删除的记录占用的空间覆盖掉

  • min_rec_mask: 记录非叶子节点中的最小记录

  • record_type: 当前记录类型

    1. 0 表示普通记录
    2. 1 表示非叶子节点
    3. 2 表示最小记录
    4. 3 表示最大记录
  • heap_no: 表示当前记录在本页中的位置(0、1是自动创建的最大记录和最小记录)

  • n_owned: 页目录中每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为这个字段的值

  • next_record: 它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量;最小记录的下一条就是主键值最小的记录,主键值最大的记录的下一条记录是最大记录

记录真实数据

  • 除了记录自定义的列数据,还会有三个隐藏列
    1. DB_ROW_ID:行ID,唯一标识一个行记录,只有在创建表时,既没有指定一个主键列,也没有Unique唯一列,那么就会自动生成一个唯一ID列
    2. DB_TRX_ID:事务ID
    3. DB_ROLL_PTR:回滚指针

Dynamic和Compressed

  • InnoDB可以将一条记录中的某些数据存储在真正的数据页之外

  • MySQL对一条记录占用的最大存储空间是有限制的,除了text或blob类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535字节

  • 这65535字节除了列本身的数据之外,好包括了一些其他信息,其中有2个byte的变长字段长度列表和1个byte的NULL值列表

  • 所以最大的容量是65533字节,如果有允许NULL的列的话,最大是65532字节

  • 那么在一个数据页的默认大小是16KB,也就是16384个字节。也就是说一个页可能连一条记录都存储不了,这就是行溢出

  • 那么在CompactRedundant行格式下,对于占用空间非常大的列,会进行分页存储,在真实记录中,该列只会存储20个字节的指针,分别指向那些分散的数据的地址,包括数据的占用大小

与Compact的区别

  • Dynamic和Compressed这两种的行格式和Compact的行格式大体相像,主要区别在于处理行溢出的行为有区别
  • Dynamic和Compressed在存放blob类型的数据时,采用完全行溢出的方式,只在列中存放20byte的指针
  • Compact和Redundant则会存储一部分真实数据(768个前缀字节)
  • 而且Compressed格式还会对存储的数据进行zlib算法压缩,对于大长度的字段能够进行有效存储

Redundant

  • 属于MySQL5.0之前的默认行格式,为了向前兼容

  • 字段长度偏移列表:会把所有列(包括隐藏列)的长度信息都记录下来

  • 缺少NULL值列表

  • Redundant的记录头信息固定为6个byte

  • compact的记录头信息相比,有两个不同

    1. Redundant多了n_field和1byte_offs_flag
    2. Redundant没有record_type属性

区、段、碎片区

为什么要有区

  • 在索引树的每一层中,所有的页都会形成一个双向链表,那么如果是以页为单位分配空间的话,那么相邻的页之间它们的实际物理位置可能会离的很远。那么在做范围查询的时候,我们只需要定位到最左边和最右边的记录,然后沿着双向链表一直扫描就可以了,但是如果页之间的物理位置很远,那就会有所谓的随机I/O,然后随机I/O是非常慢的,那么如果页之间的物理位置或者说一个链表之间的页或者相邻页之间的物理位置也相邻的话,那么在范围查询的时候就可以使用所谓的顺序I/O

  • 在引入的概念之后,一个区在物理位置上是连续的64个页,在表中数据量大的时候,不再按照页分配,而是按照区分配,那么区之间也可能是物理上连续的

为什么要有段

  • 对与范围查询,其实是对索引树的叶子节点顺序扫描,而如果不区分叶子节点和非叶子节点,把它们都放在同一个区中,那么对范围扫描的效果会大打折扣。所以对存放叶子节点的区算是一个段,而非叶子节点的区又是另一个段,也就是说一个索引会生成两个段,数据段和索引段

  • 段是逻辑上的概念,不代表物理上的连续空间,它由一些完整的区和零散的页组成

为什么要有碎片区

  • 默认情况下,只有一个聚簇索引,一个索引会有两个段,段是以区为单位申请空间的,一个区默认是1MB,那如果一个表只有几条数据,也要给它分配2MB空间,而且还不能另作他用,这样造成非常浪费

  • 所以新增一个碎片区概念,在碎片区中,并不是所有的页都是为了存储同一个段的数据而存在,而是可以零散存在,页A是段A的,页B是段B的

  • 碎片区是直属表空间的,不属于任何段

  • 所以在刚刚开始往表中插入数据时,段是从某个碎片区中以单个页为单位分配空间

  • 当某个段已经占用了32个碎片区的页之后,就会以区为单位申请空间

区的分类

  1. 空闲的区(FREE)
  2. 有剩余空间的碎片区(FREE_FRAG)
  3. 没有空间的碎片区(FULL_FRAG)
  4. 属于某个段的区(FSEG)

表空间

  • 表空间是InnoDB逻辑结构的最高层,表空间是一个逻辑容器,可以有一个或多个段

独立表空间

  • 每张表都有自己的表空间,所有数据和索引信息都会保存在自己的表空间,在磁盘上的表示为*.ibd文件
posted @   huang1993  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
· DeepSeek “源神”启动!「GitHub 热点速览」
点击右上角即可分享
微信分享提示