目录
⻚结构
⾏结构
⻚结构
分析过程:1.前面介绍了每个数据⻚默认为 16KB ,是操作系统"数据块" 4KB的整数倍,那么只要保证⻚的⼤⼩是操作系统"数据块" ⼤⼩的整数倍是不是也可以呢,答案是肯定的。2.MySQL提供了⼀个专⻔的系统变量来控制⻚的⼤⼩,可以通过系统变量 innodb_page_size 进⾏调整与查看,在调整⻚⼤⼩的时候需要保证设置的值是操作系统"数据块" 4KB的整数倍,从⽽保证通过操作系统和磁盘交互时"数据块"的完整性,不被分割或浪费,所以规定了innodb_page_size 可以设置的值,分别是 4096 、 8192 、 16384 、 32768 、 65536 ,对应 4KB 、 8KB 、 16KB 、 32KB 、 64KB解答问题:可以通过系统变量 innodb_page_size 进⾏调整与查看,但要保证设置的值是操作系统"数据块" 4KB的整数倍,MySQL规定 innodb_page_size 可以设置的值,分别是 4096 、8192 、 16384 、 32768 、 65536 ,对应 4KB 、 8KB 、 16KB 、 32KB 、 64KB
2.⻚都有哪些分类?我们需要重点学习哪种⻚?
解答问题:
(1)InnoDB在不同的使⽤场景定义多种不同类型的⻚,常⽤的有 数据⻚ 、 Undo Log ⻚ 、Change Buffer ⻚ 、 Extent Descriptor(XDES) ⻚ 、 InnoDB 段信息⻚ 等,每种⻚的数据结构都不相同,其中最需要我们关注的就是数据⻚,由于InnoDB中有个概念叫 "索引即数据",所以也叫做索引⻚。(2)不论哪种类型的⻚都具有⻚头(File Header)和⻚尾(File Trailer)两个信息
3.⻚头和⻚尾具体包含了哪些信息?
前置知识:
1.数据页是MySQL中定义的一个存储结构,也是保存数据行的容器
2.要讨论其结构的话,首先要明白的三个问题:
(1)数据页具有哪些属性? ---对数据页进行描述
(2) 数据页之间如何进行关联? ---通过双向链表连接每个页
当前区的最后一个页与后一个区的第一个页通过双向链表的方式关联
(3) 数据页中如何组织数据行
通过页主体的具体功能实现
解答问题:⻚头和⻚尾中包含的是⽤来描述⽂件相关的信息,如下图所示
1.⻚头 - File Header
(1)⻚号: FIL_PAGE_OFFSET 占⽤ 4Byte ,相当于⻚的⾝份证号,通过这个⻓度可以计算出每个InnoDB表中最多可以拥有 2^(4*8)-1约42亿 个⻚,表空间第⼀个⻚编号从0开始,之后的⻚号分别是1,2,3…依此类推,具体⻚的偏移量计算公式为:⻚号 * 每⻚⼤⼩;那么按照每个⻚默认16KB⼤⼩计算,⼀个表空间最⼤容量为 2^(4*8) * 16KB = 64TB ,这也是InnoDB表空间最⼤容量是64T的原因;
(2)上⼀⻚⻚号: FIL_PAGE_PREV
(3)下⼀⻚⻚号: FIL_PAGE_NEXT 多个⻚通过这两个信息组成双向链表,即使不同的⻚地址不连续,也可以通过链表连接
(4)表空间ID: FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID ,当前⻚属于哪个表空间
(5)⻚类型: FIL_PAGE_TYPE ,数据⻚对应的⻚类型是 FIL_PAGE_INDEX = 0x45BF(常量)
(6)最近⼀次修改的LSN: FIL_PAGE_LSN ,占⽤8Byte
(7)已被刷到磁盘的LSN: FIL_PAGE_FILE_FLUSH_LSN ,占⽤8Byte,(6)和(7)与RedoLog相关(8)校验和: FIL_PAGE_SPACE_OR_CHKSUM ,⽤于⻚的完整性校验2. ⻚尾 - File Trailer(1)最近⼀次修改的LSN(2)校验和:对应⻚头中的校验和如果在数据传输的过程中数据丢失或异常中断,导致⼀个数据⻚不完整就可以通过⻚头和⻚尾的校验和进⾏验证,验证算法默认使⽤ CRC32衍生问题:1. 什么是LSN?LSN:是"Log Sequence Number"的缩写,表⽰⽇志序号。⽤⼀个任意的、不断增加的值表⽰⽇志中记录的操作对应的时间点,⽤8字节的⽆符号⻓整形表⽰2.除了⻚头和⻚尾,数据⻚中还有哪些信息?⻚头和⻚尾中的各个字段描述了当前⻚的类型以及在⽂件系统中的位置,也就是说通过⻚头可以找到对应的⻚。数据⻚的主要功能是保存数据,在⼀个数据⻚中,除了⻚头与⻚尾占⽤的46个字节之外的空间都⽤来存储真正的数据,也就是数据⾏,数据⾏会与表⾥的数据⾏⼀⼀对应,基于这⼀特性MySQL也被称为 " ⾏式数据库 " ,也可以把除了⻚头⻚尾的区域称为⻚主体3.⻚主体中包含哪些信息?⻚主体中的信息都是和数据相关的,其中包括刚才提到了数据⾏,还有为了提⾼查询效率的⻚⽬录 Page Directory 和为了⽅便操作和管理数据⻚的数据⻚头 Page Header
4.数据⾏有哪些信息组成?
分析过程:1.数据行也有自身的一些属性2.数据行之间也需要建立关联关系3.数据⾏主要存储真实数据,为了⽅便数据的管理与描述,InnoDB在每个数据⾏中还添加了⼀些额外(管理)信息,于是每⼀个 DYNAMIC 数据⾏都可以划分为两部分,⼀部分存储额外信息,⼀部分存储真实数据,额外信息部分包含变⻓字段⻓度列表和NULL值列表两个⼤⼩不确定的区域,以及固定占5字节及40BIT的头信息区域,头信息中存储了⾏的基本信息,包括⾏在⻚内的位置 heap_no 、⾏类型 record_type 、下⼀⾏的地址偏移量 next_record 等6项信息,如下图所⽰:
解答问题:1.数据⾏可以划分为两部分,⼀部分存储额外信息,⼀部分存储真实数据2.额外信息部分包含变⻓字段⻓度列表和NULL值列表两个⼤⼩不确定的区域,以及固定占5字节的头信息区域衍⽣问题:1. 数据⾏是如何组织在⼀起的?数据⾏通过下⼀⾏的地址偏移量,即 next_record 将⻚内所有数据⾏组成了⼀个单向链表,这⾥要注意的是,地址偏移量指向的是下⼀⾏中真实数据的起始地址,这样做的好处是,向右是真实数据,向左就是头信息,⽽⽆需额外的⻓度计算
2.怎么标识新⻚中的第⼀⾏和最后⼀⾏?
由于每个数据行的长度不同,所以每个数据页中的数据行数也不同
当遍历⻚中的⾏时,从哪⾥开始到哪⾥结束呢?为了解决这个问题,每当创建⼀个新⻚,都会⾃动分配两个⾏,⼀个是⾏类型为2的最⼩⾏ Infimun heap_no 位置固定为0号,和⼀个是⾏类型为3的最⼤⾏ Supremun , heap_no 位置固定为1号,这两个⾏并不存储任何真实信息,⽽是做为数据⾏链表的头和尾,虽然不存储真实数据,但它们的数据结构和真实数据⾏完全⼀致,只不过数据区域存储的是代表它们⾝份的固定字符串 Infimun 和 Supremun ,新⻚中没有数据时,最⼩⾏ Infimun 的 next_record 直接连接最⼤⾏ Supremun ,最⼤⾏不连接任何⾏,它的 next_record 为0;在读取时,只要读到行类型为2就是开始,3则为结束,顺着next_record往下走,当next_record为0时就是结束位置,表示该页遍历完了,要去下一页继续寻找
3.当向⼀个新⻚插⼊数据时是如何执⾏的?当向⼀个新⻚插⼊数据时, heap_no 会从 2 号开始递增,表⽰当前记录在⻚⾯堆中的相对位置;如果是真实数据则 record_type 为0,如果是索引⽬录(B+树⾮叶节点)数据则record_type 为1;再将 Infimun 连接第⼀个数据⾏,最后⼀⾏真实数据⾏连接 Supremun ,这样数据⾏就构建成了⼀个单向链表,更多的⾏数据插⼊后,会按照主键从⼩到⼤的顺序进⾏链接;为了使⻚的结构更加清晰,通常将⻚中有数据⾏的区域称为⽤⼾数据区 User Records ,把未被数据⾏占⽤的区域称为空闲区 Free Space
5.如果要查询的数据在某⼀个⻚中,如何定位它在⻚中的位置,⼀条条遍历吗?
前置知识:说明已经将数据定位到此数据页了
分析过程:
1.⼀条条遍历的查询效率⾼不⾼?
从头开始遍历是⼀个最简单的⽅法,也可以实现数据的查找,当按主键或索引查找某条数据时,从头⾏ infimun 开始,沿着链表顺序逐个⽐对查找,但⼀个⻚有16KB,通常会存在数百⾏数据,每次都要遍历数百⾏,⽆法满⾜⾼效查询。2.如何提⾼⻚内的查询效率?⻚⽬录(1) 为了提⾼查询效率,InnoDB采⽤⼆分查找来解决查询效率问题(2) 在每⼀个⻚中加⼊⼀个叫做⻚⽬录 Page Directory 的结构,将⻚内包括头⾏、尾⾏在内的所有⾏进⾏分组约定头⾏单独为⼀组,其他每个组最多8条数据同时把每个组最后⼀⾏在⻚中的地址,按主键从⼩到⼤的顺序记录在⻚⽬录中在,⻚⽬录中的每⼀个位置称为⼀个槽,每个槽都对应了⼀个分组在插⼊数据⾏完成链接后,⼀旦最后⼀个分组中的数据⾏超 过分组的上限8个时,就会分裂出⼀个新的分组为了快速判断每个分组是否达到了8个的上限,在每个分组最后⼀⾏中⽤ n_owned 记录了这个分组内的⾏数,与此同时在⻚⽬录中创建⼀个新的槽,后续插⼊的⾏都遵守这个规则;3. 后续在查询某⾏时,就可以通过⼆分查找,先找到对应的槽,然后在槽内最多8个数据⾏中进⾏遍 历即可,从⽽⼤幅提⾼了查询效率;4. 例如要查找主键为6的⾏,先⽐对槽中记录的主键值,定位到最后⼀个槽2,再从最后⼀个槽中的 ⼀条记录遍历,第⼆条记录就是我们要查询的⽬标⾏
解决问题:
为了提⾼查询效率,在每⼀个⻚中加⼊⼀个叫做⻚⽬录 Page Directory 的结构,采⽤⼆分查找来解决查询效率问题。
6.关于事务、索引这些信息在⻚中怎么记录?
前置知识:
数据页的作用主要是记录用户数据的,即记录数据行,在执行sql语句时,比如修改操作,都会包含在一个事务中,再如插入新数据时,对应的索引也需要记录,所以关于数据页所管理的一些数据信息都需要被记录下来
解答问题:
一张图就可以明确的表⽰出这些信息在⻚中是怎么记录的
7.数据⻚的完整结构是什么样的?
解答问题:
注:这⾥讲的是InnoDB的数据⻚结构,和MyISAM的⻚结构有所不同
数据⻚的完整结构,以及所占的磁盘空间如图所示
⾄此⼀个⻚的核⼼结构就介绍完了,主要内容包括:1. 设置⻚的⼤⼩2. ⻚的主要分类3. ⻚头和⻚尾包含的信息4. ⻚主体的组成部分5. 数据⾏的组成部分6. 数据⻚头包含的统计和描述信息
⾏结构
1.InnoDB⽀持的数据⾏格式都有哪些?
解答问题
InnoDB⽀持四种⾏格式,分别是: REDUNDANT 冗余格式, COMPACT 紧凑格式, DYNAMIC 动态格式和 COMPRESSED 压缩格式,默认是 DYNAMIC 格式衍⽣问题
1.如何查看当前数据库或表应⽤了哪种⾏格式?
# 查看系统变量中设置的⾏格式
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)
2.如何指定⾏格式?
可以通过全局变量设置⾏格式,也可以在创建表中通过 ROW_FORMAT ⼦句指定⾏格式:
# 通过全局变量设置
SET GLOBAL innodb_default_row_format=DYNAMIC;
# 在创建表时明确的指定⾏格式
CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;
3.DYNAMIC 格式由哪些部分组成?
⼀个 DYNAMIC 格式的数据⾏会被分为两部分,⼀个部是存储真实数据的区域,⼀部分是存储额外信息的区域
2.数据区是怎么存储真实数据的?
解答问题:
1.数据区在数据⾏中的位置如下图所⽰:
2.从分隔线向右第⼀个字段存储真实数据的主键值,对于主键值有以下⼏种情况:
3.紧接着是在事务运⾏中两个⾮常重要的固定字段(每个DML操作都会包含在一个事务中)
(1)6字节的事务ID字段 DB_TX_ID ,记录创建或最后⼀次修改该记录的事务ID(2)7字节的回滚指针字段 DB_ROLL_PTR ,如果在事务中这条记录被修改,指向这条记录的上⼀个版本4.接下来就是除了主键和值为NULL的列之外,其他列的真实数据,按照顺序从左到右依次排列5. 为什么不存储NULL值,原因很简单,就是为了节少空间,所有允许为NULL的列都会在⾏额外 信息区的NULL值列表中进⾏标识
3.额外(管理)信息区包含了关于⾏的哪些信息?
解答问题:
额外信息区在数据⾏中的位置如下图所⽰:额外信息区从右向左分别为:头信息,Null值列表,变⻓字段列表。
4.头信息区域包含了哪些信息?
解答问题:分隔线向左是额外信息区,第⼀个是固定占5Byte即40个Bit的头信息区域,头信息区由右向左主要包含以下信息:
(1)下⼀⾏地址偏移量: next_record 占16bit,通过这个信息将所有的⾏链接成⼀个单向链表(2)⾏类型: record_type 占3bit,包括四种类型:0:普通数据⾏1:索引⽬录⾏2:⻚内最⼩⾏infimun3:⻚内最⼤⾏supremun(3)⾏在整个⻚中的位置: heap_no 占13bit;(4)分组的⾏数: n_owned 占4bit,只在该⾏是分组最后⼀⾏才有值,这样就可以快速查询⾏数,⽽不⽤⼀条条的累加了(5)B+树索引树每层最⼩值标记: min_rec_flag 占1bit,如果当前⾏的类型是⽬录⾏也就是record_type=1 ,同时也是B+索引树某层的最⼩值,则会置为1,会在索引查询时⽤到(6)删除标记: delete_mask 占 1bit ,从⻚中删除数据⾏时,并不会直接移除,⽽是修改这个删除标记为 1(7)预留区:占2bit
衍⽣问题:1.删除⼀⾏记录时在InnoDB内部执⾏了哪些操作?从⻚中删除数据⾏时,并不会直接移除,⽽是修改 delete_mask 这个删除标记为 1 ,并将next_record 改为 0 ,同时将上⼀⾏的 next_record 指向后续的⾏,从⽽把该⾏从链表中断开,如果执⾏事务提交后,则将这⾏的 next_record 指向⼀个被称为垃圾链表的区域,这个链表会被⽤在事务回滚中
5.Null列表有啥作⽤?列表中的值是什么?
解答问题:
1.头信息区再向右就是NULL值列表的可变区域,⽤来存储数据⾏中所有列允许为Null的值从⽽节省空间,具体的实现⽅式是,⽤1BIT的⼤⼩来表⽰⾏中某⼀列是否为空,这样空列就不需要记录在真实数据区域中了2. 为每个没有定义 NOT NULL 约束也就是可以为NULL的列在NULL值列表中都安排了⼀个bit位,按 列序号从⼩到⼤的顺序从右⾄左依序安排,这就是常说的逆序排列,NULL值列表最⼩1字节即 8bit,如果没有那么多可以为NULL的列,则会⽤0补满8bit,如果为值为NULL的列超过8个,则新 开辟1字节的空间,依此类推;3. 如果某列为空,则NULL值列表中对应的bit设置为1,这样只⽤了⼀bit就存储了NULL列,⾮常节省 空间
如:
6.变⻓字段列表有啥作⽤?列表中的值是什么?
前置知识:
1.可以影响一列实际长度的因素
(1)列的类型是一个可变长度类型 ,varchar(M),text,blob等
(2)建表时指定的字符编码集
查看编码集所占的字节数 show charset
mysql> show charset;
+----------+---------------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+---------------------------------+---------------------+--------+
| gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 |
| gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
.......//省略
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| gb18030 | China National Standard GB18030 | gb18030_chinese_ci | 4 |
+----------+---------------------------------+---------------------+--------+
41 rows in set (0.00 sec)
Maxlen为一个字符用的Byte数
使用的是utf8mb4的话
2.一个建表操作
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`)
);
解答问题:
1.⾏结构的最左侧是变⻓字段列表,也叫可变字段⻓度列表,在这个列表中记录了数据⾏中所有变⻓字段的实际⻓度,这样做的⽬的,是为了在真实数据区域,可以根据列的⻓度进⾏列与列之间的分割,保证数据的完整性;,如果不记录的话,就可能会导致少读数据/多读数据
2.需要记录的变⻓字段类型常⻅的有varchar、varbinary、text、blob,以及当使⽤了例如utf-8、gbk等变⻓字符集的char类型,当char类型的字节数可能超过768个字节时,⽐如使⽤utf8mb4字符集时定义了char(255),这个字段的最⼤字节数是4*255=1020
3.每个变⻓字段分配1 ~ 2个字节来存放这些字段的真实⼤⼩,放置顺序也是按表中字段的顺序从右⾄左逆序排列;
4.2个字节最⼤可以表⽰65535个字节,按照最⼤⻓度字符串,⽐如 utf8mb4,⼀个字符占⽤最多4个字节计算,2个字节最多可以表⽰65535/4=16383个字符,列数据类型varchar的⻓度上限16383就是根据这个计算来的;
;
# 看这个创建表的例⼦
CREATE TABLE test_varchar (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(20000) NULL,
PRIMARY KEY (`id`)
);
# 返回错误
ERROR 1074 (42000): Column length too big for column 'name' (max = 16383); use
BLOB or TEXT instead
5.如果text、blob存储的内容过⼤,⼀个⻚已经不够放了,就会把这个列放⼊⼀个叫"溢出⻚"的独⽴空间中,在这个数据⾏对应的真实数据处,只使⽤20个字节来标记这个溢出⻚的位置信息,在InnoDB中规定,一个数据页中至少要放两个数据行,默认16KB的页大小中,一个数据行最大为8KB
衍⽣问题:1. 如何记录变⻓字段的实际⻓度?(1) 不同的字符集在处理字符对应的最⼤字节⻓度不同,以如 ascii 最⼤1个字节, utf8mb3 最⼤3 个字节, utf8mb4 最⼤4个字节(2) 当使⽤varchar( M )指定⼀个字段的最⼤字符数时,该字段真实使⽤的字节数与建表时指定的字符 集有关,如果指定的字符集单个字符最⼤占 W 个字节,从理论上讲,该列最多使⽤的字节数 M * W ,如果 M * W <= 255 则⽤⼀个字节记录这个变⻓字段的⻓度就⾜够了(3) 如果 M * W > 255 可能分为两种情况,假设当前实际变⻓字段实现占⽤了 L 个字节:L <= 127 ⽤⼀个字节表⽰⻓度L > 127 ⽤两个字节表⽰⻓度2.读取⻓度时如何处理粘包问题?也就是说在读取变⻓字段⻓度时,如何确定读取⼀个字节还是两个字节?(1)在任何时候都是先读⼀个字节,然后判断这个字节的⾼位是否为0,如果是0则表⽰当前⽤⼀个字节表⽰⻓度,如果是1则表⽰当前⽤两个字节表⽰⻓度(2) 为1时再读⼀个字节,然后合并在⼀起进⾏解析得到该字段真实的使⽤的字节数,⽽且第⼆个BIT位 表⽰是否使⽤溢出⻚
⼩结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有什么区别?
解答问题:
(1)REDUNDANT 冗余格式已被淘汰,之所以存在是为了与旧版本 MySQL 兼容,不建议使⽤,这⾥不再讨论。(2) COMPRESSED 压缩格式⾏结构与 DYNAMIC 完全相同,只是会对数据进⾏压缩,以减少对空间的占⽤。(3) COMPACT 紧凑格式在结构上与 DYNAMIC 相同,只是对超⻓字段的处理上有些区别,它不会把所有超⻓数据都放在溢出⻚中,⽽是会在本⾏中保留前768个字节的数据,多出的部分放在溢出⻚中,溢出⻚的地址额外⽤20个字节表⽰,那么在本⾏的列中就会占⽤768+20个字节
对于上面就是InnoDB表涉及的所有存储结构,这里用一个图将其串联下