mysql基础-Innodb逻辑存储结构

通过阅读本文可以了解什么?

  • 磁盘预读概念
  • 数据页结构
  • innoDb的行格式
  • 行溢出
  • VARCHAR(65535) 大小的列能不能创建
  • 怎么定位到一条数据
  • 为什么尽可能设置为非NULL字段

从InnoDB存储引擎的逻辑存储结构来看,所有的数据都被逻辑逻辑的存放在一个空间中,成为表空间。表空间又由 段、区、页组成。本文着重将页和行记录相关的数据

磁盘预读

mysql服务器是我们在生产环境中常用的服务器,大部分的存储引擎数据都是写入到磁盘中的而服务器每次读取数据时会先从pagecache读取数据,如果pagecache中没有,需要从磁盘进行读取。

我们知道当我们从磁盘读取数据时,需要经过磁盘寻址加旋转的过程,相对于从缓存中读数据来说是很慢的,所以为了保证数据的读取效率,mysql服务器不是按需读取,而是进行磁盘预读

预读的长度一般为页(page)的整倍数。磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中。

数据页结构

定义

页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。

InnoDB中的实现

InnoDB中页的大小一般为 16 KB,也就是说,当需要从磁盘中读数据时 每一次最少将从磁盘中读取16KB的内容到内存中,每一次最少也会把内存中的16KB内容写到磁盘中。

默认的页大小为 16KB,每个页中至少存储有 2 条或以上的行记录

从InnoDB 1.2X开始 我们可以通过参数 innodb_page_size
将页的大小设置为4K、8K、16K

页的类别

  1. 数据页(B-tree Node)
  2. undo 页 (undo Log Page)
  3. 系统页 (System Page)
  4. 事物数据页(Transaction system page)
  5. 插入缓冲空闲列表页 (Insert Buffer Free List)
  6. 插入缓冲位图页(Insert Buffer Bitmap)
  7. 未压缩的二进制大对象页(UnCompresser BLOB Page)
  8. 压缩的二进制大对象页(Compressed BLOB Page)

查看默认页的大小

SHOW GLOBAL STATUS like 'Innodb_page_size';

页的结构

页结构

各模块作用
名称 占用空间 描述
File Header 38字节 页的一些通用信息
Page Header 56字节 数据页专有的一些信息
Infimum + Supremum 26字节 两个虚拟的行记录
User Records 不确定 实际存储的行记录内容
Free Space 不确定 页中尚未使用的空间,被删除的行记录会被记录成空闲空间
Page Directory 不确定 页中的某些记录的相对位置,二叉查找相关的信息
File Trailer 8字节 存储用于检测数据完整性的校验和等数据
File Heade

各种页的通用信息,固定为38个字节

名称 占用空间大小(字节) 描述
FIL_PAGE_SPACE_OR_CHKSUM 4 页的校验和(checksum值)
FIL_PAGE_OFFSET 4 页号,InnoDB通过页号来可以唯一定位一个页
FIL_PAGE_PREV 4 上一个页的页号
FIL_PAGE_NEXT 4 下一个页的页号
FIL_PAGE_LSN 8 页面被最后修改时对应的日志序列位置(Log Sequence Number)
FIL_PAGE_TYPE 2 该页的类型 记住0x45BF 代表数据页
FIL_PAGE_FILE_FLUSH_LSN 8 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 对于独立表空间该值都为0
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 页属于哪个表空间

其中比较重要的是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT 字段,通过这两个字段,我们可以找到该页的上一页和下一页,实际上所有页通过两个字段可以形成一条双向链表
并不是所有页都有这个属性的,但是索引页有。

Page Header

Page Header 字段用于记录 Page 的状态信息

如下结构仅针对索引页

名称 占用空间大小(字节) 描述
PAGE_N_DIR_SLOTS 2 在页目录中的槽数量
PAGE_HEAP_TOP 2 还未使用的空间最小地址,也就是说从该地址之后就是Free Space
PAGE_N_HEAP 2 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)
PAGE_FREE 2 第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)
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_DIRECTION:
假如新插入的一条记录的主键值比上一条记录的主键值小,我们说这条记录的插入方向是左边,反之则是右边。
PAGE_DIRECTION用来表示最后一条记录插入方向的状态。

PAGE_N_DIRECTION:
假设连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记到PAGE_N_DIRECTION这个状态上。
如果最后一条记录的插入方向改变了,这个状态的值会被清零。

Infimum 和 Supremum

Infimum 和 Supremum 是两个伪行记录,Infimum(下确界)记录比该页中任何主键值都要小的值,Supremum (上确界)记录比该页中任何主键值都要大的值,这个伪记录分别构成了页中记录的边界。

Page Directory

存储二分查找槽的信息 (页的相对位置而不是偏移量)

  • 将所有正常的记录划分为几个组。
  • 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录
  • 将每个组的最后一条记录的地址偏移量(slot)单独提取出来按顺序存储到Page Directory
  • 最小记录所在的分组只能有 1条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间即Infimum的n_owned=1,Supremum的n_owned=[1,8],其他的用户记录n_owned=[4,8]

按主键查找的顺序
1.先通过二分查找定位主键所在的slot
2.找到slot中最小的行记录
3.根据记录上的next_record属性遍历整个slot
4.返回

需要牢记的是B+数索引本身并不能找到具体一条记录,能找到的是该记录所在的页。数据库把页加载到内存,然后通过 Page Directory 再进行二分查找。只不过二分查找的复杂度低,同时在内存中的查找很快,因此通常忽略这部分查找所用的时间

InnoDB存储引擎是面向列的。也就是说数据是按行存储的。
每个页存放的行记录也是有硬性定义的,最多允许存放 16K/2-200行的记录,即7992行记录。

行格式

  • Compact
  • Redundant
  • Dynamic
  • Compressed
    通过执行 show table status like 'tableName'来查看行格式

指定行格式

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称

Compact行格式

行格式图例
从上图可以看到,一行记录逻辑上主要分为两部分:

  • 额外信息
  • 记录的真实数据

记录的额外信息

主要包含三部分:变成字段列表,NULL值列表,记录头信息

变成字段长度列表

变长字段列表中存储多少字节的数据不是固定的,实际长度取决于列数和每一列的长度。

变成字段定义

比如由 VARCHAR(M)、VARBINARY(M)、TEXT类型,BLOB类型等数据类型修饰的列称为变长字段

注意VARCHAR(M),M代表最大能存多少个字符。( MySQL5.0.3以前是字节,以后就是字符)

字节长度需要跟编码方式相关联,例如 UTF-8 一个中文字符需要 3 字节来表示,ascii一个字符就是一个字节

作用

记录变长数据的真实字节数

在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表。

特点

1.变长字段实际占用字节数以逆序方式存储在变长字段长度列表中

2.允许的最大字节超过255且实际存储超过127字节, 使用两个字节存储其长度, 否则使用一个字节

InnoDB在读字段变长列表时会先查表结构, 允许的最大字节数超过255时才会使用这个二进制位作为标识位来判断是读一个字节还是两个字节,没有超过就直接读一个字节也就不存在标识位了
变长字段的长度最大不可以超过两字节

3.变长列不为NULL时, InnoDB才会存储其字节长度

4.如果没有变长字段或为NULL,则当前记录没有此部分

5.第一个字节的第一位是标志位,表示是否双字节表示

NULL值列表

一个字节 一共8位

作用

如果有的列可以为NULL,那么 Compac行格式会把这些NULL字段统一管理起来,存一个标志位在NULL值列表中。

  • 二进制的1 代表该列为NULL
  • 二进制的0 代表该列非NULL
  • 逆序存放

如果一个表中的列都不允许为NULL 则不会存在这个列表

在建表时,字段进行都设置为非NULL,这样就可以节省这一部分空间了

记录头信息

记录头为定长结构,长度为5字节,但是每个位都代表不同的含义

作用

如下用来描述记录的相关信息

名称 长度(bit) 作用
预留位1 1 保留
预留位2 1 保留
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 标记B+树中每层非叶子节点的最小记录 (Mysql技术内幕写的是:如果该记录是被预先定义为最小记录)
n_owned 4 当前记录拥有的记录数(这个组有多少条记录)
heap_no 13 当前记录在堆中的位置
record_type 3 000:普通记录,001:B+树非叶子节点记录,010:最小记录,011:最大记录 1xx:保留
next_record 16 下一条记录的相对位置

一个页中的所有记录使用next_record 字段形成了一条单链表

记录的真实数据

记录的真实数据中除了存放真实数据之外,还有三个隐藏列

名称 长度(字节) 作用 是否必须
DB_ROW_ID 6 唯一标示一条记录
DB_TRX_ID 6 事务ID
DB_ROLL_PTR 7 回滚指针,指向一条undo日志记录

只有当一个表没有手动定义主键,且没有Unique键时,才会为表默认添加一个名为row_id(DB_ROW_ID)的隐藏列作为主键。

NULL不占该部分的任何空间,即NULL除了占有NULL标志位,实际存储不占有任何空间

行溢出数据

mysql中一条记录占用的最大存储空间是有限制的。除了Text和Blob大字段外,其余的字段(不包括隐藏列和记录头信息)加起来不能超过65535个字节。myisam存储引擎不受此限制

我们知道数据页的大小是 16KB即16384字节,Innodb 存储引擎保证了每一页至少有两条记录
一般情况下数据都存放在页类型为 B-Tree node中,但是当发生了行溢出时,会截取前768字节的前缀数据和每个溢出页的偏移量存放在B-Tree node中剩下的数据存放在多个类型为Uncompressed BLOB页中。

所以可能会存在一个页放不完数据的情况,针对这种情况,不同的行格式处理的方式不同

Compact和Reduntant

对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中。然后记录的真实数据处用20个字节存储指向这些页的地址,从而可以找到剩余数据所在的页。

20个字节中还包含每页的大小

Dynamic和Compressed

它们不会在记录的真实数据处存储一部分数据,而是把所有的数据都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

Dynamic和Compressed行格式除了上述说的处理溢出问题时和别的格式不一样,其他的都几乎一样

Compressed行格式会采用压缩算法对页面进行压缩。

注意

Mysql数据库的VARCHAR没办法存储65535字节的实际数据,这是因为还有别的开销

如果表中只有这一个字段

  • 非null情况下 不定长度列表占2字节 所以 VARCHAR的大小为 65532
  • null的情况 还要考虑一个null标志位 1字节 所以 VARCHAR的大小为 65531

文当中说的支持最大VARCHAR(65535),单位是字节。
65535 是说的所有的 VARCHAR 列的总和

猜你喜欢

转载自blog.csdn.net/azhida/article/details/106177217