当前位置: 首页 > news >正文

[MySQL数据库] InnoDB存储引擎(二) : 磁盘结构详解

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

  • 1. InnoDB存储引擎简介
    • 1.1 MySQL为什么默认使用InnoDB存储引擎?
    • 1.2 InnoDB存储引擎的架构是怎么样的?
      • 1.2.1 衍生问题1: 为什么要设计成内存结构和磁盘结构两个部分?
    • 1.3 使用InnoDB存储引擎创建的表对应的数据文件在哪里?
  • 2. MySQL存储结构
    • 2.1 什么是表空间文件?
      • 2.2.1 衍生问题: 表空间与表空间文件的关系是什么?
    • 2.2 数据在表空间中是如何存储的?
    • 2.3 为什么要使用页这个数据单元?
      • 2.3.1 衍生问题: 什么是局部性原理?
    • 2.4 页的基本结构是怎么样的?页
    • 2.5 查询的数据超过一页的大小,怎么提高查询效率?区
      • 2.5.1 前提问题
      • 2.5.2 解答问题
      • 2.5.3 衍生问题
    • 2.6 当表中的数据很少的时候如何避免浪费空间?
    • 2.7 如果访问的数据跨区了怎么办?区组
    • 2.8 以上这些数据还有优化的空间吗?段
      • 2.8.1 衍生问题1: 上面讲的所有的操作都是在哪里进行的?
      • 2.8.2 衍生问题2: 查询数据时MySQL会一次把表空间中的数据全部加载到内存吗?
      • 2.8.3 衍生问题3: 每查询一条数据都要进行一次磁盘IO吗?
  • 3. 页结构
    • 3.1 页的大小可以设置吗?
    • 3.2 页都有哪些分类?
    • 3.3 页头和页尾都包含了哪些信息?
      • 3.3.1 衍生问题1: 什么是LSN
      • 3.3.2 衍生问题2: 除了页头和页尾,数据页中还有哪些信息?
      • 3.3.3 衍生问题3: 页主体中包含哪些信息?
    • 3.4 数据行有哪些信息组成
      • 3.4.1 衍生问题1: 数据行是如何组织在一起的?
      • 3.4.2 衍生问题2: 怎么标识新页中的第一行和最后一行
      • 3.4.3 衍生问题3: 向一个新页中插入数据时是如何执行的?
    • 3.5 如果要查询的数据在某个页中,如何确定他在页中的位置,一条条的遍历吗?
      • 3.5.1 一条一条遍历效率高不高?
      • 3.5.2 如何提高页内的查询效率?页目录
    • 3.6 关于事务,索引这些信息在页中怎么记录
    • 3.7 数据页的完整结构是什么样的?
  • 4. 行结构
    • 4.1 InnoDB支持的数据行格式都有哪些?
      • 4.1.1 衍生问题1: 如何查看当前数据库或表应用了哪种行格式?
      • 4.1.2 衍生问题2: 如何指定行格式
      • 4.1.3 衍生问题3: DYNAMIC格式由哪些部分组成?
    • 4.2 数据区是怎么存储真实数据的?
    • 4.3 额外信息区包含了哪些关于行的信息?
    • 4.4 头信息区域包含哪些信息?
      • 4.4.1 衍生问题1: 删除一行记录时候,InnoDB内部执行了哪些操作?
    • 4.5 NULL列表有什么作用?列表中的值是什么?
    • 4.6 变长字段列表有什么作用? 列表中的值是什么?
      • 4.6.1 衍生问题1: 如何记录变长字段的实际长度?
      • 4.6.2 衍生问题2: 读取长度时如何处理粘包问题?

本章节,我们以问题的形式引出话题,之后我们来解答问题,并提出衍生问题

1. InnoDB存储引擎简介

1.1 MySQL为什么默认使用InnoDB存储引擎?

这也可以说在问InnoDB存储引擎有什么优点,这个问题我们在上一个章节曾经讨论过.

  • InnoDB引擎支持事务和回滚机制,具有数据的崩溃修复能力,以保证数据的完整性与安全性.
  • InnoDB引擎支持多版本并发操作,采用的是行级锁,具有很高的并发性.
  • InnoDB引擎优化了基于主键的查询,每个InnoDB表都有一个称为聚集索引的主键索引,是B+树的结构,实现通过最少的磁盘IO完成对主键的查找.
  • InnoDB支持外键索引,保证该列创建的数据在另一个表中存在,以保证数据的正确性.

我们也可以从InnoDB的结构出发:

  • InnoDB具有缓冲池,可以把经常访问的数据放入缓冲池中,避免硬盘IO拖慢查询速度.
  • 当反复查询某个行的时候,会有自适应哈希来接管这些查询,以加快查询的速率.

1.2 InnoDB存储引擎的架构是怎么样的?

InnoDB主要包括内存结构和磁盘结构.

  • 内存结构中包括: 缓冲池,变更缓冲区,日志缓冲区,自适应哈希.
  • 磁盘结构中包括: 系统表空间,独立表空间,通用表空间,临时表空间,撤销表空间,重做日志,双写缓冲区.

在这里插入图片描述

1.2.1 衍生问题1: 为什么要设计成内存结构和磁盘结构两个部分?

  • 在查询数据的时候,如果每次都从磁盘上去读取数据会严重影响效率,为了提高数据的访问效率,InnoDB会把查询到的数据缓存到内存中,当再次查询的时候,如果目标数据已经存在于内存中,则直接从内存读取,从而大幅提升效率.
  • 简单来说,磁盘结构中的文件是用来保存数据,实现数据持久化的,内存结构是用来缓存数据提升效率的.

1.3 使用InnoDB存储引擎创建的表对应的数据文件在哪里?

当使用InnoDB存储引擎创建一个表的时候,默认会在数据目录(与数据库名相同的目录)对应的数据库子目录中生成响应的表空间文件(文件名和表名相同),以.ibd文件为后缀,用来存储数据和索引(数据和索引放在同一个文件中),如果每个表都对应一个表空间文件,称为独立表空间.可以通过系统变量innodb_file_per_table[=ON|OFF]控制,如果关闭这个选项,则所有表的数据都在系统表空间中存储.

2. MySQL存储结构

2.1 什么是表空间文件?

表空间文件是用来存储表中数据的文件,表空间文件的大小由存储的数据多少决定,不同的表空间文件存储数据的种类也有所不同,在MySQL中表空间分为5类,包括: 系统表空间,独立表空间,通用表空间,临时表空间和撤销表空间.我们在上面叙述InnoDB存储引擎的时候我们就提到过.

2.2.1 衍生问题: 表空间与表空间文件的关系是什么?

表空间可以理解为MySQL为了管理数据而设计的一种数据结构,主要描述的对结构的定义,表空间文件是对定义的具体实现,以文件的形式存在于硬盘上.
表空间就相当于设计图,表空间文件是根据设计图进行施工的产物.

2.2 数据在表空间中是如何存储的?

  • 首先明确一点,用户的数据以数据行的方式存储在对应的表空间文件中,那么表空间中很多个数据就需要进行管理,以便后续进行高效的查询.
  • 为了方便管理,表空间由段,区组,区,页,数据行组成,其中页是InnoDB磁盘管理的最小单位.
  • 总结: 若干个数据行组成了一个页,若干个页组成了区,若干个区组成了区组,若干个区组组成了段.

就像我们的小区一样:段就是整个小区,区组就是楼,区就是单元楼,页就是每一层,数据行就是这一层的具体门户.
在这里插入图片描述

2.3 为什么要使用页这个数据单元?

  • 通常,操作系统中的文件系统在管理磁盘文件时以4KB大小为一个管理单元,称为"数据块",但是在数据库的应用场景里,查询时数据量都比较大,如果也使用4KB做数据存储的最小单元,就显得有点小了,会造成频繁的磁盘IO,导致降低效率.
  • 所以MySQL根据自身的情况定义了大小为16KB的页,作为磁盘管理的最小单位.
  • 每次至少读取一页(一次读16KB),所以在磁盘中的每个页内部的地址都是连续的.实际上我们只需要一条数据,这样做会不会过度去读造成资源浪费呢,不是的,是因为在使用数据库的过程中,根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是临近的,所以一次从磁盘中至少读取一页的数据放入内存中,当下次查询的数据还在这个页中的时候就可以从内存中直接读取.
  • 总结: MySQL根据自身的应用场景使用数据页作为数据管理单元,最主要的目的就是减少磁盘IO,提高性能.
    就像我们入住房子需要打扫,我们接水需要拿桶或者是盆接一样,那杯子接显得有点小.

2.3.1 衍生问题: 什么是局部性原理?

如果我们一次读取一页的话,就会引来一个问题,我们实际上只需要查询一条数据,但是从磁盘上读取了16KB,会不会造成过度读取后者资源浪费?
不会,这是由于读取数据的时候是遵循局部性原理的.
局部性原理是指程序在执行的时候呈现出局部性的规律,在一段时间内,整个程序的执行仅限于某一部分.响应的,执行所访问的存储空间也局限于某个内存区域,局部性通常有两种方式: 时间局部性和空间局部性.
时间局部性: 如果一个信息项正在被访问,那么在近期他很可能还会被再再次访问.
空间局部性: 将来要用到的信息大概率与正在使用的信息在空间上是临近的.

2.4 页的基本结构是怎么样的?页

  • 页的16KB大小是MySQL的一个默认设置,当然我们也可以根据业务场景,通过系统变量innodb_page_size进行调整,但是我们需要注意的是,我们在调整页的大小的时候需要保证设置的值是操作系统"数据块"的整数倍,从而保证通过操作系统和磁盘交互时候"数据块"的完整性,空间不被分割或者是浪费.所以规定了innodb_page_size可以设置的值,分别是4KB,8KB,16KB,32KB,64KB.
  • 在不同的使用场景之下,页的结构也会有所不同,在MySQL中有多种不同类型的页,但是不论哪种类型的页都会包含页头和页尾,在这页头和页尾之间的页主体信息根据不同的类型有不同的结构,页的主题信息使用**数据"行"**进行填充.行的结构我们在后续介绍.
    在这里插入图片描述

2.5 查询的数据超过一页的大小,怎么提高查询效率?区

2.5.1 前提问题

在搞清楚这个问题之前,我们需要明白这几个问题:

  1. 不同的页在磁盘中是否是连续的?
    不一定,在不做任何限制的情况之下,不同的也在磁盘中申请的空间大概率是不连续的.我们可以很快分析出来连续的地址对于查询效率的影响,如果也在磁盘中可以被连续读取,那么查询的效率就会高,否则效率就会低.
  2. 为什么不连续的地址会降低查询的效率?
    当存储介质是机械硬盘的时候,访问不连续的地址会带来磁盘寻址的开销,也就是磁头在不同盘面,磁道和扇区的机械转动,机械转动的过程非常影响效率.所以我们在磁盘中读取不同的页的时候,大概率会产生磁盘的随机访问,主要是由于页在磁盘中的存储空间不连续引起的.那么提高访问效率的办法就是尽量访问连续的页.
  3. InnoDB如何保证页在磁盘中的连续性?
    为了减少磁头的移动,从而提升效率,MySQL使用了这个结构来管理页,规定每个区固定大小为1MB,可以存放64个页,这时候如果跨页访问的时候,大概率都在附近的地址,可以大幅度减少碰头移动.
    同时如果频繁的读取某个区中的页,可以把整个区都读取出来放入到内存的Buffer Pool中,减少后续查询对磁盘的访问次数,进一步提升效率.
    在这里插入图片描述

2.5.2 解答问题

通过对问题的分析,我们了解到了InnoDB中用来组织数据页的数据结构–区,并且每个区固定大小为1MB,可以包含64个连续的页,查询的数据超过一页大小的时候,MySQL的InnoDB就会通过区这种数据结构来保证效率,当查询的数据超过一页大小的时候,通常分为一下几种情况:

  • 页在区内是相邻的: 磁盘顺序IO,可以大幅提升效率.
  • 页在区内但是不是相邻的: 可以大幅减少磁头移动,可以提升效率.
  • 页在不同的区: 还是要发生随机IO,不能提升效率.

2.5.3 衍生问题

新创建表时没有数据,或者说有的表只有很少的数据的时候,1MB的空间用不完,会不会造成资源浪费?
的确是这样,接下来我们继续提出问题.

2.6 当表中的数据很少的时候如何避免浪费空间?

当创建表的时候,并不知道当前表的数量级.为了节省空间,最初只是创建7个初始页,而不是一个完整的区,可以通过以下的SQL查看:

mysql> select * FROM information_schema.INNODB_TABLESPACES WHERE name = 
'test_db/student'\G
*************************** 1. row ***************************
 SPACE: 13
 NAME: test_db/student
 FLAG: 16417
 ROW_FORMAT: Dynamic
 PAGE_SIZE: 16384 # ⻚⼤⼩
 ZIP_PAGE_SIZE: 0
 SPACE_TYPE: Single
 FS_BLOCK_SIZE: 65536
 FILE_SIZE: 114688 # 数据⽂件初始⼤⼩
 ALLOCATED_SIZE: 114688
AUTOEXTEND_SIZE: 0
 SERVER_VERSION: 8.0.33
 SPACE_VERSION: 1
 ENCRYPTION: N
 STATE: normal
1 row in set (0.01 sec)
# 根据数据⽂件⼤⼩和每⻚⼤⼩计算出⻚数
# 114688 / 16384 = 7 个数据⻚

这些零散的页会放在表空间中一个叫碎片区的区域.随着数量的增加,会申请新的数据页来存储数据,当碎片区达到32个页的时候,后续每次都会申请一个完整的区来存储更多的区域.
在这里插入图片描述

2.7 如果访问的数据跨区了怎么办?区组

和页一样,不同的区在磁盘上大概率也是不连续的,那么这个问题其实是InnoDB如何高效的管理区?
当表中的数据越来越多,为了有效的管理区,定义了区组的结构.每个区组固定管理256个区,即256MB,通过区组可以在物理结构层面非常高效的管理和定位到每个区.每个区组中是这样组织区的:

  • 区的偏移量
  • 区的管理数据范围
  • 之后用双向链表把区组织起来
    在这里插入图片描述
  • 第一个区组中的首个区的前四页比较特殊,也就是初始页中的前四页,分别是:
    ◦ File Space Header: 表空间和区组中条目信息
    ◦ Insert Buffer Bitmap:Change Buffer相关信息
    ◦ File Segment inode: 段信息
    ◦ B-tree Node:索引根信息
    ◦ 其他为空闲页,用来存储真实的数据
  • 其他区组中首个区的结构都一样,前两个页分别是:
    ◦ Extent Descriptor(XDES):区组条目信息
    ◦ Insert Buffer Bitmap:Change Buffer相关信息
  • 总结: 使用区组结构有效的管理区,每个区组固定管理256个区即256MB,区组条目信息中会记录每个区的偏移即维护数据的区域,并用双向链表把每个区链接起来.

2.8 以上这些数据还有优化的空间吗?段

以上讲到的区,区组,还有页这种都是物理结构
在物理结构的基础上,定义了一个逻辑上的概念,就是"段".
之所以叫逻辑结构,是因为"段"并不对应表空间中的连续物理区域,可以看做是"区"和"页"的一个附加标注信息.段的主要作用是区分不同功能的区和在碎片区中的页,按照功能主要分为"叶子结点段"和"非叶子结点段"等,这两个段和我们常说的B+树的索引中的叶子,非叶子结点对应,可以简单的理解为"非叶子结点段"存储和管理索引,"叶子结点段"存储和管理实际的数据,从逻辑上讲,最终由"叶子结点段"和"非叶子结点段"等段构成了表空间.ibd文件,如下图所示:
在这里插入图片描述

  • 总结: InnoDB使用了"段"这个逻辑结构区分不同功能的区和在碎片区中的页.并按照功能分为"叶子节点段"和"非叶子结点段",作为B+树索引中的叶子,非叶子结点,从而进一步提升查询效率.

2.8.1 衍生问题1: 上面讲的所有的操作都是在哪里进行的?

所有的数据操作都是在内存中进行的,在操作过内存之后,会形成脏数据,最终会把修改结构刷会到磁盘对应的页中.

2.8.2 衍生问题2: 查询数据时MySQL会一次把表空间中的数据全部加载到内存吗?

当然不是,使用InnoDB存储引擎创建表,在查询数据的时候会根据表空间内部定义的数据结构(一般为索引),定位到目标数据所在的页,只把符合查询要求的页加载到内存中.

2.8.3 衍生问题3: 每查询一条数据都要进行一次磁盘IO吗?

不一定,每一次查询都会把磁盘中的数据行对应的页加载到内存中,如果当前查询的数据行已经在内存中的某一页存在,则直接从内存中返回结果,从而提高查询效率.

3. 页结构

页有以下的性质:

  1. 页是InnoDB中磁盘管理的最小单位
  2. 页是磁盘与内存交互过程中的最小单位.
  3. 每次进行磁盘IO读取最少一页数据.
  4. InnoDB中使用的B+树索引也是基于页实现的.
  5. 页与页之间会通过双向链表的方式来进行关联.

3.1 页的大小可以设置吗?

我们前面介绍了每个数据页默认为16KB,是操作系统"数据块"4KB的整数倍,那么是要保证页的大小是操作系统"数据块"大小的倍数是不是也可以呢,答案是肯定的.
MySQL提供了一个专门的系统变量来控制大小,可以通过系统变量innodb_page_size进行调整与查看,在调整页大小的时候需要保证设置的值是操作系统"数据块"4KB的整数倍,从而保证通过操作系统和磁盘交互式的"数据块"的完整性,不被分割或者浪费,所以规定了innodb_page_size可以设置的值,分别是4096,8192,16384,32768,65535,对应4KB,8KB,16KB,32KB,64KB.

  • 总结: 可以通过系统变量innodb_page_size进行调整与查看,在调整页大小的时候需要保证设置的值是操作系统"数据块"4KB的整数倍,从而保证通过操作系统和磁盘交互式的"数据块"的完整性,不被分割或者浪费,所以规定了innodb_page_size可以设置的值,分别是4096,8192,16384,32768,65535,对应4KB,8KB,16KB,32KB,64KB

3.2 页都有哪些分类?

InnoDB在不同的使用场景定义了多种不同类型的页,常用的有数据页,Undo log页,ChangeBuffer页,XDES页,InnoDB段信息页等,每种页的数据结构都不同,其中最需要我们关注的就是数据页,由于InnoDB中有一个概念叫做"索引即数据",所以也叫索引页.
不论哪种类型的页都具有页头和页尾两个信息.

3.3 页头和页尾都包含了哪些信息?

在这里插入图片描述

  1. 页头-File Header
    包含页号,上一页页号,下一页页号(页号顾名思义就是对页进行编号的),表空间ID(标识当前页属于哪一个表空间),页类型(一般通过枚举或者是常量的方式指定),最近一次修改的LSN,以被刷到磁盘的LSN,校验和(用户校验页的完整性).
  2. 页尾-File Trailer
    最近一次修改的LSN,校验和(对应页头中的校验和)

如果在数据传输的过程中数据丢失或者是异常中断,导致一个数据页不完整就可以通过页头和页尾的校验和进行验证,验证算法默认使用CRC32.

3.3.1 衍生问题1: 什么是LSN

LSN: 是"log sequence Number"的缩写,表示日志序号,用一个任意的,不断增加的日志中记录的操作对应的时间点,用8字节的无符号长整型表示.

3.3.2 衍生问题2: 除了页头和页尾,数据页中还有哪些信息?

数据页的主要功能是保存数据,在一个数据页中,除了页头与页尾占用的46个字节之外的空间都用来存储真正的数据,也就是数据行,数据行会与表里的数据行一一对应,基于这一特性的MySQL也被称为"行式数据库",也可以把除了页头和页尾的区域称为页主体.

3.3.3 衍生问题3: 页主体中包含哪些信息?

页主体中的信息都是和数据相关的,其中包括刚才提到的数据行,还有为了提高查询效率的页目录和为了方便操作和管理数据页的数据页头.这又是三个非常重要的概念,接下来我们逐个讨论.

3.4 数据行有哪些信息组成

数据行主要存储真实的数据,为了方便数据的管理与描述,InnoDB在每个数据行中还添加了一些额外的管理信息,于是每个DYNAMIC数据行都可以划分为两部分,一部分存储额外的信息,一部分存储真实的数据,额外信息部分包含变长字段长度列表和NULL值列表两个大小不确定的区域,以及固定的5字节40BIT的头信息区域头信息区域,头信息中存储了行的基本嘻嘻,包括在页内的位置heap_no,行类型record_Type,下一行的地址偏移量next_record等6项信息,如下图所示:
在这里插入图片描述

  • 总结: 数据行可以分为两部分,一部分存储额外的信息,一部分存储真实的数据.额外信息部分包含变长字段长度列表和NULL值列表两个大小不确定的区域,以及固定的5字节的头信息区域.

3.4.1 衍生问题1: 数据行是如何组织在一起的?

  • 数据行通过下一行的地址偏移量,next_record将页内所有数据行组成一个单向链表,这里需要注意的是,地址偏移量指向的是下一行中真实数据的起始地址,这样做的好处是,向右是真实的数据,向左就是头信息,而无需额外的长度计算,如图所示:
    在这里插入图片描述

3.4.2 衍生问题2: 怎么标识新页中的第一行和最后一行

了解了行的基本结构和组织方式之后,那么当遍历页中的行时,从哪里开始到哪里结束呢?为了解决这个问题,每当创建一个新页,都会自动分配两个行,一是行类型为2的最小行Infimun,heap_no位置固定位0号,和一个是行类型为3的最大行Supernum,heap_no位置固定位1号,这两个行并不存储任何信息,而是作为数据行链表的头和尾,虽然不存储真实的数据,但是它们的数据结构和真实数据行完全一致,只不过数据区域存储的是代表它们身份的固定字符串InfimunSupremun,新页中没有数据时,最小行Infimunnext_record直接连接最大行Supremun,最大行不连接任何行,它的next_record为0.
在这里插入图片描述

3.4.3 衍生问题3: 向一个新页中插入数据时是如何执行的?

当向一个新页插入数据的时候,heap_no会动2号开始递增,表示当前记录在页面堆中的相对位置,如果是真实数据则record_type为0,如果是索引目录数据则record_type为1,再将Infimun连接第一个数据行,最后一行真实数据行连接Supremun,这样的数据行就构建成了一个单向链表,更多的行数据插入之后,会按照主键从小到大的顺序进行连接,为了使得页的结构更加清晰,通常将页中有数据行的区域称为用户数据区UserRecords,把未被数据行占用的区域称为空闲区Free Space,如下图所示:
在这里插入图片描述

3.5 如果要查询的数据在某个页中,如何确定他在页中的位置,一条条的遍历吗?

不是,InnoDB使用了一种更高效的查询方式,我们下面来分析:

3.5.1 一条一条遍历效率高不高?

从头开始遍历是一个最简单的方法,也可以实现数据的查找,从头行infimun开始,沿着链表顺序逐个对比查找,但一个页有16KB,通常会存在百行数据,每次都需要遍历数百行,无法满足高效查询.

3.5.2 如何提高页内的查询效率?页目录

为了提高查询效率,InnoDB采用的是二分查找来解决查询效率问题的.
具体的实现是,在每个页中加入一个叫做页目录的结构,将页内包括头行,尾行在内的所有进行分组,约定头行单独为一组,其他每个组最多8条数据,同时把每个组最后一行在页中的地址,按主键从小到大的顺序在页目录中,一旦最后一个分组中的数据超过分组的上限8个时,就会分裂出一个新的分组,为了快速判断每个分组是否达到了8个的上限,在每个分组最后一行中用n_owned记录了这个分组内的行数,与此同时在页目录中创建一个新的槽,后续插入的行都遵循这个规则.
后续在查询的时候,就可以通过二分查找的方式(这种查找方式其实有点像跳表),先找到对应的槽,然后在槽内最多8个数据中进行遍历即可,从而大幅提高查询效率.
比如我们要查找主键为6的行,先比对槽中记录的主键值,定位到最后一个槽2,再从最后一个槽的第一条遍历,第二条记录就是我们要查询的目标行.
在这里插入图片描述

  • 总结: 为了提高查找效率,在每个页中加入了一个叫做页目录的结构,采用二分查找来解决查询效率的问题.

3.6 关于事务,索引这些信息在页中怎么记录

这些信息一般记录在数据页头中.
在这里插入图片描述

3.7 数据页的完整结构是什么样的?

需要注意的是,这里表示的是InnoDB的数据页结构,和MyISAM的页结构有所不同.

在这里插入图片描述

4. 行结构

真实的数据在表空间以数据行的形式存储,也就是说每一条数据都对应着表中的一行,数据行在页中的位置如下图:
在这里插入图片描述
InnoDB支持4种行格式,不同的行格式数据的存储上有所不同,接下来我们介绍关于行结构的内容,首先先提出一个问题:

4.1 InnoDB支持的数据行格式都有哪些?

InnoDB支持4中行格式,分别是: REDUNDANT 冗余格式, COMPACT 紧凑格式, DYNAMIC 动态格式和 COMPRESSED 压缩格式,默认是 DYNAMIC 格式.

4.1.1 衍生问题1: 如何查看当前数据库或表应用了哪种行格式?

可以使用以下SQL查看行格式:

# 查看系统变量中设置的⾏格式
mysql> SHOW VARIABLES LIKE 'innodb_default_row_format';
+---------------------------+---------+
| Variable_name | Value |
+---------------------------+---------+
| innodb_default_row_format | dynamic | # 当前使⽤的⾏格式
+---------------------------+---------+
1 row in set, 1 warning (0.02 sec)
# 使⽤SHOW table STATUS查看数据库中的所有表
mysql> SHOW TABLE STATUS IN test_db\G
# ... 省略
*************************** 6. row ***************************
 Name: student
 Engine: InnoDB
 Version: 10
 Row_format: Dynamic # 指定数据库使⽤的⾏格式
 Rows: 5
 Avg_row_length: 3276
 Data_length: 16384
Max_data_length: 0
Index_length: 0
 Data_free: 0
 Auto_increment: NULL
 Create_time: 2023-09-19 15:27:27
 Update_time: NULL
 Check_time: NULL
 Collation: utf8mb4_general_ci
 Checksum: NULL
 Create_options: row_format=DYNAMIC
 Comment:
# ... 省略
8 rows in set (0.04 sec)
# 通过查询INFORMATION_SCHEMA.INNODB_TABLES表查看指定表的⾏格式
mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE 
NAME='test_db/student';
+-----------------+------------+
| NAME | ROW_FORMAT |
+-----------------+------------+
| test_db/student | Dynamic | # 指定表使⽤的⾏格式
+-----------------+------------+
1 row in set (0.00 sec)

4.1.2 衍生问题2: 如何指定行格式

可以通过全局变量设置行格式,也可以在创还表中通过ROW_FORMAT子句制定行格式:

# 通过全局变量设置
SET GLOBAL innodb_default_row_format=DYNAMIC;
# 在创建表时明确的指定⾏格式
CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;

4.1.3 衍生问题3: DYNAMIC格式由哪些部分组成?

一个DYNAMIC格式的数据行会被分为两个部分,一个是存储真实数据的区域,一部分是存储额外信息的区域,在页结构的小结已经对行做了简单的介绍,下面来详细讲解一下行的组成结构.

4.2 数据区是怎么存储真实数据的?

数据区在数据行中的位置如下:
在这里插入图片描述
从分割线向右第一个字段存储真实数据的主键值,对于主键值有以下的几种情况:

  • 如果表中定义了主键,则直接存储主键的值.
  • 如果是复合主键会根据列定义的顺序依次排列在这里.
  • 如果没有主键,会优先使用第一个不允许为NULL的UNIQUE唯一列作为主键.
  • 如果既没有主键也没有唯一键,那么InnoDB会构建一个6字节的字段DB_ROW_ID作为行的唯一标识,存储在真实数据的头部.

紧接着是在事务运行中两个非常重要的固定字段:

  • 6字节的事务ID字段DB_TX_ID,记录创建或最后一次修改该记录的事务ID.
  • 7字节的回滚指针字段DB_ROLL_PTR,如果在事务中这条记录被修改,指向这条记录的上一个版本.

接下来就是除了主键和值为NULL的列之外,其他的列的真实数据,按照顺序从左到右一次排列.之所以不存储NULL值,就是为了减少空间的使用.

在这里插入图片描述

4.3 额外信息区包含了哪些关于行的信息?

额外信息区在数据行中的位置如下图所示:
在这里插入图片描述
从右向左分别是: 头信息,NULL值列表,变长字段列表.

4.4 头信息区域包含哪些信息?

在这里插入图片描述

  • 下一行的地址偏移量: next_record,通过这个信息将所有的行链接成一个单向链表.
  • 行类型: record_type,包括四种类型: 0普通数据行,1索引目录行,2页内最小行,3页内最大行.
  • 行在整个页中占的位置: heap_no.
  • 分组的行数: n_woned,只在该行是分组最后一行才有值,这样就可以快速查询行数,而不需要一条一条的累加.
  • b+树索引树每层最小值标记: min_rec_flag,如果当前行的类型是目录行也就是record_type=1,同时也是b+索引树某层的最小值,则会置为1,会在索引查询时候用到.
  • 删除标记: delete_mask,从页中删除数据行的时候,并不会直接删除,而是修改这个删除标记为1.
  • 预留区

4.4.1 衍生问题1: 删除一行记录时候,InnoDB内部执行了哪些操作?

从页中删除数据时,并不会直接删除,而是修改delete_mask这个删除标记为1,并将next_record改为0,同时将上一行的next_record直系那个后续的行,从而把行从列表中断开,如果执行的事务提交之后,则将行的next_record指向一个被称为垃圾链表的区域,这个链表会被用在事务回滚中,后续我们介绍事务的时候详细讲解.

在这里插入图片描述

4.5 NULL列表有什么作用?列表中的值是什么?

  • 头信息区在向右就是NULL值列表的可变区域,用来存储数据行中所有允许为NULL的值从而节省空间,具体的实现是,用1BIT的大小来表示行中某一页是否为空,这样空列就不需要记录在真实数据区了.
  • 为每个没有定义NOT NULL约束,也就是可以为NULL的列在NULL值列表中都安排了一个bit位,按列序号从小到大的顺序从右到左安排.如果某列为空,则NULL值列表中对应的bit设置为1,这样只用了一个bit就存储了NULL列,非常节省空间.
  • NULL值列表最小为1字节,即8bit,如果没有那么多可以为NULL的列,则会用0把8bit补满.
    在这里插入图片描述

4.6 变长字段列表有什么作用? 列表中的值是什么?

  • 在变长字段列表中,这个列表中记录了数据行中所有变长字段的实际长度,这样做的目的,是为了在真实数据区域,可以根据列的长度进行行列与列之间的分割.
  • 需要记录的变长字段类型常见的有varchar,varbinary,text,blob,以及当使用了例如utf-8,gbk等变长字符集的char类型.
  • 需要特别说明的是,如果text,blob存储内容过大,一个页已经不够放的时候,就会把这个列放入一个叫做"溢出页"的独立空间中.
    在这里插入图片描述

4.6.1 衍生问题1: 如何记录变长字段的实际长度?

  • 不同的字符集在处理字符对应的最大字节长度不同,比如ASCII最大1个字节,UTF-8MB3最大3个字节,UTF-8MB4最大4个字节.
  • 当使用varchar(M)指定一个字段的最大字符数时,该字段真实使用的字节数与建表时指定的字符集有关系,如果指定的字符集的那个字符集最大占W个字节,从理论上讲,该列最多使用的字节数M * W,如果M * W <= 255则用一个字节记录这个变长字段的长度就够了.
  • 如果M * W > 255可能分为两种情况,假设当前边长字段实现占用了L字节,L <= 127用一个字节表示长度,L > 127用两个字节表示长度.

4.6.2 衍生问题2: 读取长度时如何处理粘包问题?

  • 也就是说在读取边长字段长度时,如何确定读取一个字节还是两个字节?
  • 在任何时候都是先读取一个字节,然后判断这个字节的高位是否为0,如果是0则表示当前用一个字节表示长度,如果是1则表示当前用两个字节表示的长度.
  • 为1时在读一个字节,然后合并在一起进行解析得到该字段真实的使用字节数,而且第二个bit位表示是否使用溢出页.

在这里插入图片描述
至此,InnoDB表涉及的所有磁盘存储结构全部介绍完成,我们使用一张图来总结:
在这里插入图片描述

相关文章:

  • 智慧景区能源管理解决方案,为旅游“升温”保驾护航
  • 不用第三方库调用DeepSeek
  • Go语言从零构建SQL数据库(6) - sql解析器(番外)- *号的处理
  • React 列表渲染
  • 算法(0)-时间复杂度-二分法的详解与扩展-对数器-C++版
  • cmake阅读笔记
  • HTTP代理:内容分发战场上的「隐形指挥官」
  • Lettuce与Springboot集成使用
  • 蓝桥杯-数字诗意
  • MTK-Android12-13 屏幕永不休眠功能实现
  • 落子宜宾:全方位解析树莓集团现状布局
  • Vue3+Vite+TypeScript+Element Plus开发-06.Header响应式菜单缩展
  • AIDD-人工智能药物设计-TCMP-12个公开的中药数据库
  • window上 docker使用ros2开发并usbip共享usb设备
  • C# 中的 nameof 表达式:用法详解与最佳实践
  • Vue3 + Vite + TS,使用 Web Worker,web worker进阶 hooks
  • Qt中自定义插件和库(1)
  • 深入理解Docker Bridge网络模式:原理与实践指南
  • leetcode_203. 移除链表元素_java
  • 网络安全法规与入门指南
  • 做美工需要知道的设计网站/关键词排名优化流程
  • 如何 做网站的推广/湖南有实力seo优化
  • 最近军事新闻热点大事件2022/济南专业seo推广公司
  • 咸阳网站制作建设/关键词推广怎么做
  • perl 网站开发/windows优化大师破解版
  • 网站建设zvge/惠州疫情最新消息