一. 前言
一行数据的存储格式大致如下所示:
变长字段的长度列表,null值列表,数据头,column01的值,column02的值,column0n的值…
二. 变长字段
在MySQL里有一些字段的长度是变长的,是不固定的,比如VARCHAR(255)之类的这种类型的字段,实际上他里面存放的字符串的长度是不固定的,有可能是“hello”这么一个字符串,也可能是“hello word”这么一个字符串。
- 示例1
格式 | VARCHAR(10) | CHAR(1) | CHAR(1) |
---|---|---|---|
数据 | hello | a | a |
存储方式 | 0x05 null值列表 数据头 hello a a |
---|
- 示例2
多个变长字段, 是逆序存储
格式 | VARCHAR(10) | VARCHAR(5) | VARCHAR(20) | CHAR(1) | CHAR(1) |
---|---|---|---|---|---|
数据 | hello | a2 | aa3 | a | a |
存储方式 | 0x05 0x02 0x03 null值列表 数据头 hello a a |
---|
- 为什么要逆序存储.
在 MySQL 中,对于变长字段(如 VARCHAR),其长度信息在存储时确实可能以逆序方式存储,这主要涉及到存储格式和性能优化的考量。这个特性尤其在早期版本的 MySQL 中比较明显,而在新版本中,由于存储格式的改进和优化,这种差异可能不那么直观。以下是一些可能导致逆序存储长度信息的原因:
### 1. 存储效率
在某些场景下,将变长字段的长度信息以逆序方式存储可以提高存储效率。例如,如果一个表包含多个变长字段,MySQL 需要在记录的开始处存储这些字段的长度信息。通过逆序存储这些长度信息,MySQL 可以更有效地处理记录中字段值的增减变化,因为这样做可以减少因字段长度变化而导致的数据移动。
### 2. 记录格式
MySQL 中,尤其是 InnoDB 存储引擎,采用了多种记录格式(如 COMPACT 和 REDUNDANT)。在某些记录格式中,字段长度的存储方式可能被设计为逆序,部分原因是为了优化记录的解析过程。在解析一条记录时,从记录的末尾开始可以更快地定位到可变长度字段的实际内容,尤其是在需要跳过某些字段以访问特定字段时。
### 3. 兼容性和历史原因
MySQL 的不同版本和存储引擎在记录格式和存储策略上可能有所不同。一些设计决策,包括字段长度信息的存储方式,可能受到向后兼容性和历史遗留问题的影响。随着 MySQL 版本的发展,存储格式和算法不断优化,但在一些情况下,逆序存储长度信息的方式可能被保留下来,以确保数据的一致性和有效的性能表现。
三.mysql innodb_default_row_format 可以配置那些值, 有什么区别
在MySQL中,innodb_default_row_format
选项指定了InnoDB表创建时的默认行格式。这个设置对于新创建的表非常重要,因为它影响了数据的存储方式、空间利用率和性能。innodb_default_row_format
可以配置为以下几个值,每个值代表不同的行格式:
可配置的值及其区别
-
Redundant
- 这是早期版本的InnoDB行格式,默认使用在MySQL 5.0及更早版本中。
- 它提供了最基本的特性集合,但在空间利用率和性能方面并不是最优的。
Redundant
行格式支持较小的最大行大小和较少的数据类型优化。
-
Compact
- 从MySQL 5.0版本引入,是一个比
Redundant
更高效的行格式,旨在改进空间利用率。 - 它减少了头信息的尺寸,并采用了更高效的方式存储
NULL
值和可变长度字段。 - 对于许多应用而言,
Compact
是一个好的默认选择,因为它提供了较好的空间效率而不牺牲太多性能。
- 从MySQL 5.0版本引入,是一个比
-
Dynamic
- 在MySQL 5.7.9及以后版本中,
Dynamic
成为了默认的行格式。 - 它解决了
Compact
格式中一些限制,特别是关于BLOB和TEXT类型字段的更高效存储。在Dynamic
格式中,这些类型的字段只在行中存储20字节的指针,实际数据存储在外部页面。 Dynamic
行格式提供了更好的空间利用率,特别是对于包含大量BLOB或TEXT字段的表。
- 在MySQL 5.7.9及以后版本中,
-
Compressed
Compressed
行格式允许对整行数据进行压缩,进一步节省存储空间。- 它特别适用于那些磁盘空间成本高昂或者需要存储大量历史数据的环境。
- 使用
Compressed
格式可能会对CPU资源产生额外负担,因为需要在读写时进行压缩和解压缩操作。
选择合适的行格式
选择哪种行格式取决于具体的应用需求。Dynamic
和Compressed
格式在空间利用率方面提供了显著的优势,特别是对于包含大量长文本或BLOB字段的表。然而,Compressed
格式可能会引入一定的性能开销。对于大多数应用,Dynamic
行格式提供了一个很好的平衡点,特别是在MySQL 5.7.9及以后版本中,它成为了默认选择。
配置方法
在MySQL中,你可以通过几种方式查询默认的行格式(row format)。默认的行格式是指当你创建一个新表且没有指定行格式时,MySQL所使用的行格式。下面介绍两种常见的方法来查询默认行格式:
-
- 查看全局变量
MySQL允许你通过查询全局系统变量来了解当前的默认行格式。你可以使用以下SQL命令查询默认的行格式:
SHOW VARIABLES LIKE 'innodb_default_row_format';
这条命令会返回innodb_default_row_format
变量的值,该值表示InnoDB存储引擎默认使用的行格式。可能的返回值包括Redundant
、Compact
、Dynamic
等。
-
- 查看服务器配置文件
MySQL的默认行格式也可以在服务器的配置文件(通常是my.cnf
或my.ini
,具体取决于你的操作系统)中进行设置和查看。在配置文件中搜索innodb_default_row_format
这一行。例如:
[mysqld]
innodb_default_row_format=dynamic
如果在配置文件中设置了该参数,它将决定默认的行格式。请注意,更改配置文件需要重启MySQL服务器才能生效。
-
附加信息
-
MySQL版本的影响:不同版本的MySQL可能有不同的默认行格式设置。例如,在MySQL 5.7.9及以后版本中,默认行格式是
DYNAMIC
。 -
修改默认行格式:你可以通过修改配置文件或设置全局变量的方式来更改默认的行格式,但请注意更改默认行格式可能会影响新创建表的性能和存储效率。
-
表级行格式设置:即使更改了默认行格式,你仍然可以在创建表时通过
ROW_FORMAT
选项指定特定表的行格式,这将覆盖全局默认设置。
四. NULL值列表
对所有的NULL值,不通过字符串在磁盘上存储,而是通过二进制的bit位来存储,一行数据里假设有多个字段的值都是NULL,那么这多个字段的NULL,就会以bit位的形式存放在NULL值列表中。
- 示例
建表语句
CREATE TABLE customer (
name VARCHAR(10) NOT NULL,
address VARCHAR(20),
gender CHAR(1),
job VARCHAR(30),
school VARCHAR(50)
) ROW_FORMAT=COMPACT;
上面那个表就是一个假想出来的客户表,里面有5个字段,分别为name、address、genderjob、school,就代表了客户的姓名、地址、性别、工作以及学校。
其中有4个变长字段,还有一个定长字段,然后第一个name字段是声明了NOT NULL的,就是不能为NULL,其他4个字段都可能是NULL的。
存储数据: “jack NULL m NULL xx_school”
- 先看变长字段长度列表
一共有4个变长字段, 变长字段的值是NULL,就不用在变长字段长度列表里存放他的值长度了. 所以在上面那行数据中,只有name和school两个变长字段是有值的,把他们的长度按照逆序放在变长字段长度列表中就可以了
0x09 0x04 NULL值列表 头信息 column1=value1 column2=value2 … columnN=valueN
- NULL值列表
允许值为NULL,不是说一定值就是NULL了,只要是允许你为NULL的字段,在这里每个字段都有一个二进制bit位的值,如果bit值是1说明是NULL,如果bit值是0说明不是NULL。
比如上面4个字段都允许为NULL,每个人都会有一个bit位,这一行数据的值是“jack NULL m NULL xx_school”,然后其中2个字段是null,2个字段不是null,所以4个bit位应该是:1010
NULL值列表存放的时候,他一般是8个bit位的倍数,如果不足8个bit位就高位补0
0x09 0x04 00000101 头信息 column1=value1 column2=value2 … columnN=valueN
五. 数据头 (40个bit)
数据头是用来描述这行数据
这40个bit位里,第一个bit位和第二个bit位,都是预留位,是没任何含义的
一个bit位是delete_mask,他标识的是这行数据是否被删除了
1-2 | 3 : delete_mask | 4: min_rec_mask | 5-9 : n_owned | 10-23: heap_no | 24-26: record_type | 27-40: next_record |
---|---|---|---|---|---|---|
预留位 | 是否被删除 | 在B+树里每一层的非叶子节点里的最小值, 用于指示是否有记录具有相同的键值位于它之后, 用于优化记录的读取 | 在B+树索引结构中,一个索引记录(record)“拥有”的相邻记录数 | 当前这行数据在记录堆里的位置 | 这行数据的类型: 0代表的是普通类型,1代表的是B+树非叶子节点,2代表的是最小值数据,3代表的是最大值数据 | 指向他下一条数据的指针 |
六. 字符串存储(根据字符集编码存储)
表结构:
CREATE TABLE customer (
name VARCHAR(10) NOT NULL,
address VARCHAR(20),
gender CHAR(1),
job VARCHAR(30),
school VARCHAR(50)
) ROW_FORMAT=COMPACT;
存储数据: jack NULL m NULL xx_school
真实存储类似 (非最终存储结构, 字符串尚未编码):
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school
字符串根据数据库指定的字符集编码,进行编码之后再存储的
编码后的格式(非最终存储结构):
0x09 0x04 00000101 0000000000000000000010000000000000011001 616161 636320 6262626262
在实际存储一行数据的时候,会在他的真实数据部分,加入一些隐藏字段
-
首先有一个DB_ROW_ID字段,这就是一个行的唯一标识,是他数据库内部的一个标识,不是你的主键ID字段。
如果没有指定主键和unique key唯一索引的时候,他就内部自动加一个ROW_ID作为主键。 -
接着是一个DB_TRX_ID字段,这是跟事务相关的,他是说这是哪个事务更新的数据,这是事务ID
-
最后是DB_ROLL_PTR字段,这是回滚指针,是用来进行事务回滚的
实际一行数据存储(最终)
0x09 0x04 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID)00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161 636320 6262626262
七. 行溢出
在MySQL的InnoDB存储引擎中,“行溢出”(Row Overflow)是指一行数据过大,无法完全存储在一个数据库页(Page)中时发生的情况。这通常发生在包含大量文本或二进制数据(如BLOB或TEXT类型字段)的行上。InnoDB的页面大小通常为16KB,当一行数据的总大小超过这个限制时,就会发生行溢出。
-
为什么会有行溢出?
InnoDB使用B+树存储机制来组织数据表和索引。每个B+树节点(页)有一个最大的空间限制,默认情况下是16KB。大多数行数据都能完整地存储在单个页内,但当遇到特别大的字段值时,如大文本或BLOB字段,整行数据可能无法适应单个页的空间限制。 -
行溢出的处理机制:
当InnoDB遇到一个无法适应单个页的大行时,它采用的策略是将主要的行数据保存在原始页中,但会将大字段的一部分或全部移动到其他页(溢出页)上。原始记录页中的大字段位置会被一个指针替代,这个指针指向存储实际数据的溢出页。 -
行溢出的影响:
性能影响:访问涉及行溢出的数据可能需要额外的磁盘I/O操作,因为需要读取一个或多个额外的溢出页来获取完整的行数据。这种额外的I/O开销可能会导致性能下降,特别是在大量随机访问的场景中。
空间利用:虽然行溢出机制允许InnoDB表存储大于页面大小的行数据,但这种方式可能导致存储空间的利用率不是很高。溢出页可能不会完全填满,特别是当存储非常大的字段时。
- 如何管理行溢出:
适当的表设计:尽可能避免在表中使用大量的大型字段。如果应用确实需要存储大量文本或二进制数据,考虑将这些数据分割成较小的部分,或者使用外部存储系统。
选择合适的行格式:对于包含大字段的表,使用DYNAMIC或COMPRESSED行格式可能更合适。这些行格式提供了更高效的行溢出数据管理机制。
监控和优化:使用工具和命令(如SHOW TABLE STATUS)来监控表的大小和行格式,以及定期进行数据库优化,可以帮助管理和减轻行溢出的影响。
八. 数据页
MySQL中进行数据操作的最小单位应该是数据页
执行crud的时候,都会从磁盘上加载数据页到Buffer Pool的缓存页里去,然后更新了缓存页后,又会刷新回磁盘上的数据页里去
数据页拆分成了很多个部分,大体上来说包含了文件头、数据页头、最小记录和最大记录、多个数据行、空闲空间、数据页目录、文件尾部。
- 数据区域存储占比
其中文件头占据了38个字节,
数据页头占据了56个字节,
最大记录和最小记录占据了26个字节,
数据行区域的大小是不固定的,
空闲区域的大小也是不固定的,
数据页目录的大小也是不固定的,
然后文件尾部占据8个字节。
九. 表空间
表都是有对应的表空间的,每个表空间就是对应了磁盘上的数据文件,在表空间里有很多组数据区,一组数据区是256个数据区,每个数据区包含了64个数据页,是1mb
表空间的第一组数据区的第一个数据区的头三个数据页,都是存放特殊信息的;
表空间的其他组数据区的第一个数据区的头两个数据页,也都是存放特殊信息的
创建的表,其实都是有一个表空间的概念,在磁盘上都会对应着“表名.ibd”这样的一个磁盘数据文件
在物理层面,表空间就是对应一些磁盘上的数据文件。
有的表空间,比如系统表空间可能对应的是多个磁盘文件,有的我们自己创建的表对应的表空间可能就是对应了一个“表名.ibd”数据文件。
然后在表空间的磁盘文件里,会有很多很多的数据页,
一个表空间里包含的数据页实在是太多了,不便于管理,所以在表空间里又引入了一个数据区的概念,英文就是extent
一个数据区对应着连续的64个数据页,每个数据页是16kb,所以一个数据区是1mb,然后256个数据区被划分为了一组。
对于表空间而言,他的第一组数据区的第一个数据区的前3个数据页,都是固定的,里面存放了一些描述性的数据。比如FSP_HDR这个数据页,他里面就存放了表空间和这一组数据区的一些属性。
IBUF_BITMAP数据页,里面存放的是这一组数据页的所有insert buffer的一些信息。
INODE数据页,这里也是存放了一些特殊的信息
然后这个表空间里的其他各组数据区,每一组数据区的第一个数据区的头两个数据页,都是存放特殊信息的,比如XDES数据页就是用来存放这一组数据区的一些相关属性的,其实就是很多描述这组数据区的东西