【mysql进阶】4-4. 行结构

时间:2024-10-27 14:44:07

行结构

真实的数据在表空间以数据⾏的形式存储,也就是说每⼀条数据都对应着表中的⼀⾏,数据⾏在⻚中的位置如下图所⽰:

image-20241025224849856

  • InnoDB⽀持四种⾏格式,不同的⾏格式数据的存储上有所不同,接下来我们介绍关于⾏结构的内容,⾸先提出第⼀个问题:

1 InnoDB⽀持的数据⾏格式都有哪些?

✅ 解答问题

  • nnoDB⽀持四种⾏格式,分别是: REDUNDANT 冗余格式, COMPACT 紧凑格式, DYNAMIC 动态格式和 COMPRESSED 压缩格式,默认是 DYNAMIC 格式。

???? 衍⽣问题

1.1 如何查看当前数据库或表应⽤了哪种⾏格式?

  • 可以使⽤以下SQL查看⾏格式:

image-20241025225409497

image-20241025225423334

1.2 如何指定⾏格式?

  • 可以通过全局变量设置⾏格式,也可以在创建表中通过 ROW_FORMAT ⼦句指定⾏格式:

image-20241025225450803

1.3 DYNAMIC 格式由哪些部分组成?

⼀个 DYNAMIC 格式的数据⾏会被分为两部分,⼀个部是存储真实数据的区域,⼀部分是存储额外信息的区域,在⻚结构的⼩节已经对⾏做了简单介绍,下⾯来详细讲解⼀下⾏的组成结构

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

✅ 解答问题

  • 数据区在数据⾏中的位置如下图所⽰:

image-20241025225607123

  • 从分隔线向右第⼀个字段存储真实数据的主键值,对于主键值有以下⼏种情况:

    • 如果表中定义了主键,则直接存储主键的值;

    • 如果是复合主键会根据列定义的顺序依次排列在这⾥;

    • 如果没有主键,会优先使⽤第⼀个不允许为NULL的 UNIQUE 唯⼀列作为主键;

    • 如果既没有主键也没有唯⼀键,那么InnoDB会构建⼀个6字节的字段 DB_ROW_ID 作为⾏的唯⼀标识,存储在真实数据的头部

  • 紧接着是在事务运⾏中两个⾮常重要的固定字段

    • 6字节的事务ID字段 DB_TX_ID ,记录创建或最后⼀次修改该记录的事务ID
    • 7字节的回滚指针字段 DB_ROLL_PTR ,如果在事务中这条记录被修改,指向这条记录的上⼀个版本
  • 接下来就是除了主键和值为NULL的列之外,其他列的真实数据,按照顺序从左到右依次排列

  • ⾄于为什么不存储NULL值,原因很简单,就是为了节少空间,所有允许为NULL的列都会在⾏额外信息区的NULL值列表中进⾏标识,后⾯我们会详细详解,以上就是数据⾏对真实数据的存储⽅式。

image-20241025225903736

3 额外(管理)信息区包含了关于⾏的哪些信息?

✅ 解答问题

  • 额外信息区在数据⾏中的位置如下图所⽰:

image-20241025230143412

  • 额外信息区从右向左分别为:头信息,Null值列表,变⻓字段列表。

4 头信息区域包含了哪些信息?

✅ 解答问题

  • 分隔线向左是额外信息区,第⼀个是固定占5Byte即40个Bit的头信息区域,头信息区由右向左主要包含以下信息:

image-20241025230337601

  • 下⼀⾏地址偏移量: next_record 占16bit,通过这个信息将所有的⾏链接成⼀个单向链表
  • ⾏类型: record_type 占3bit,包括四种类型:
    • 0:普通数据⾏
    • 1:索引⽬录⾏
    • 2:⻚内最⼩⾏infimun
    • 3:⻚内最⼤⾏supremun
  • ⾏在整个⻚中的位置: heap_no 占13bit;
  • 分组的⾏数: n_owned 占4bit,只在该⾏是分组最后⼀⾏才有值,这样就可以快速查询⾏数,⽽不⽤⼀条条的累加了
  • B+树索引树每层最⼩值标记: min_rec_flag 占1bit,如果当前⾏的类型是⽬录⾏也就是record_type=1 ,同时也是B+索引树某层的最⼩值,则会置为1,会在索引查询时⽤到,后⾯我们讲索引时再介绍
  • 删除标记: delete_mask 占 1bit ,从⻚中删除数据⾏时,并不会直接移除,⽽是修改这个删除标记为 1
  • 预留区:占2bit

4.1 删除⼀⾏记录时在InnoDB内部执⾏了哪些操作?

  • 从⻚中删除数据⾏时,并不会直接移除,⽽是修改 delete_mask 这个删除标记为 1 ,并将next_record 改为 0 ,同时将上⼀⾏的 next_record 指向后续的⾏,从⽽把该⾏从链表中断开,如果执⾏事务提交后,则将这⾏的 next_record 指向⼀个被称为垃圾链表的区域,这个链表会被⽤在事务回滚中

image-20241025230609783

5 Null列表有啥作⽤?列表中的值是什么?

✅ 解答问题

  • 头信息区再向右就是NULL值列表的可变区域,⽤来存储数据⾏中所有列允许为Null的值从⽽节省空间,具体的实现⽅式是,⽤1BIT的⼤⼩来表⽰⾏中某⼀列是否为空,这样空列就不需要记录在真实数据区域中了
  • 为每个没有定义 NOT NULL 约束也就是可以为NULL的列在NULL值列表中都安排了⼀个bit位,按列序号从⼩到⼤的顺序从右⾄左依序安排,这就是常说的逆序排列,NULL值列表最⼩1字节即8bit,如果没有那么多可以为NULL的列,则会⽤0补满8bit,如果为值为NULL的列超过8个,则新开辟1字节的空间,依此类推;
  • 如果某列为空,则NULL值列表中对应的bit设置为1,这样只⽤了⼀bit就存储了NULL列,⾮常节省空间

image-20241025231107603

6 变⻓字段列表有啥作⽤?列表中的值是什么?

image-20241025231142489

  • 以下是⼀个建表的SQL语句
CREATE TABLE test_student (
 `id` bigint NOT NULL AUTO_INCREMENT,
 `sn` char(10) NOT NULL,
 `name` varchar(50) NOT NULL,
 `age` int NOT NULL,
 `mail` varchar(100) NOT NULL,
 `remark` varchar(255) NULL,
 PRIMARY KEY (`id`)
);

✅ 解答问题

  • ⾏结构的最左侧是变⻓字段列表,也叫可变字段⻓度列表,在这个列表中记录了数据⾏中所有变⻓字段的实际⻓度,这样做的⽬的,是为了在真实数据区域,可以根据列的⻓度进⾏列与列之间的分割;
  • 需要记录的变⻓字段类型常⻅的有varchar、varbinary、text、blob,以及当使⽤了例如utf-8、gbk等变⻓字符集的char类型,当char类型的字节数可能超过768个字节时,⽐如使⽤utf8mb4字符集时定义了char(255),这个字段的最⼤字节数是4*255=1020
  • 每个变⻓字段分配1 ~ 2个字节来存放这些字段的真实⼤⼩,放置顺序也是按表中字段的顺序从右⾄左逆序排列;

image-20241025231504734

  • 2个字节最⼤可以表⽰65535个字节,按照最⼤⻓度字符串,⽐如 utf8mb4,⼀个字符占⽤最多4个字节计算,2个字节最多可以表⽰65535/4=16383个字符,列数据类型varchar的⻓度上限16383就是根据这个计算来的;
  • 需要特别说明的是,如果text、blob存储的内容过⼤,⼀个⻚已经不够放了,就会把这个列放⼊⼀个叫"溢出⻚"的独⽴空间中,在这个数据⾏对应的真实数据处,只使⽤20个字节来标记这个溢出⻚的位置信息

image-20241025231627032

???? 衍⽣问题

6.1 如何记录变⻓字段的实际⻓度?

  • 不同的字符集在处理字符对应的最⼤字节⻓度不同,以如 ascii 最⼤1个字节, utf8mb3 最⼤3个字节, utf8mb4 最⼤4个字节,如下所⽰

image-20241025231708458

  • 当使⽤varchar( M )指定⼀个字段的最⼤字符数时,该字段真实使⽤的字节数与建表时指定的字符集有关,如果指定的字符集单个字符最⼤占 W 个字节,从理论上讲,该列最多使⽤的字节数 M * W ,如果 M * W <= 255 则⽤⼀个字节记录这个变⻓字段的⻓度就⾜够了
  • 如果 M * W > 255 可能分为两种情况,假设当前变⻓字段实现占⽤了 L 个字节:
    • L <= 127 ⽤⼀个字节表⽰⻓度
    • L > 127 ⽤两个字节表⽰⻓度

6.2 读取⻓度时如何处理粘包问题?

  • 也就是说在读取变⻓字段⻓度时,如何确定读取⼀个字节还是两个字节?
  • 在任何时候都是先读⼀个字节,然后判断这个字节的⾼位是否为0,如果是0则表⽰当前⽤⼀个字节表⽰⻓度,如果是1则表⽰当前⽤两个字节表⽰⻓度
  • 为1时再读⼀个字节,然后合并在⼀起进⾏解析得到该字段真实的使⽤的字节数,⽽且第⼆个BIT位表⽰是否使⽤溢出⻚

默认数据⻚⼤⼩为16KB,数据⻚中⼀个数据⾏的⼤⼩最⼤为8KB

image-20241025231910822

❤ ⼩结

  1. InnoDB⽀持四种⾏格式,分别是:

    ​ a. REDUNDANT 冗余格式

    ​ b. COMPACT 紧凑格式

    ​ c. DYNAMIC 动态格式(默认)

    ​ d. COMPRESSED 压缩格式

  2. DYNAMIC 格式的数据⾏由两部分组成,分别是真实数据区和额外信息区

  3. 真实数据区存储的是真实数据,有三个隐藏字段分别是:

    a. DB_ROW_ID 作为⾏的唯⼀标识

    b. DB_TX_ID 事务ID字段

    c. DB_ROLL_PTR 回滚指针字段

  4. 额外信息区从右向左分别为:

    ​ a. 头信息

    ​ b. Null值列表

    ​ c. 变⻓字段列表。

7 其他的⾏格式与DYNAMIC有什么区别?

✅ 解答问题

7.1 REDUNDANT 冗余格式

已被淘汰,之所以存在是为了与旧版本 MySQL 兼容,不建议使⽤,这⾥不再讨论。

7.2 COMPRESSED 压缩格式

⾏结构与 DYNAMIC 完全相同,只是会对数据进⾏压缩,以减少对空间的占⽤。

7.3 COMPACT 紧凑格式

在结构上与 DYNAMIC 相同,只是对超⻓字段的处理上有些区别,它不会把所有超⻓数据都放在溢出⻚中,⽽是会在本⾏中保留前768个字节的数据,多出的部分放在溢出⻚中,溢出⻚的地址额外⽤20个字节表⽰,那么在本⾏的列中就会占⽤768+20个字节。



⾄此InnoDB表涉及的所有存储结构都介绍完了,下⾯⽤⼀张图把知识点串联⼀下,帮助⼤家复习总结,如图所⽰:

MPACT 紧凑格式

在结构上与 DYNAMIC 相同,只是对超⻓字段的处理上有些区别,它不会把所有超⻓数据都放在溢出⻚中,⽽是会在本⾏中保留前768个字节的数据,多出的部分放在溢出⻚中,溢出⻚的地址额外⽤20个字节表⽰,那么在本⾏的列中就会占⽤768+20个字节。



⾄此InnoDB表涉及的所有存储结构都介绍完了,下⾯⽤⼀张图把知识点串联⼀下,帮助⼤家复习总结,如图所⽰:

image-20241025232423907