页:InnoDB
将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,页的大小一般为 16 KB。
行格式:我们平时以记录为单位向表中插入数据,这些记录在磁盘上的存放方式被称为行格式
或者记录格式
。
InnoDB
存储引擎行格式类型: Compact
、Redundant
、Dynamic
、Compressed
在创建或修改表的语句中指定行格式
:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称ALTER TABLE 表名 ROW_FORMAT=行格式名称
COMPACT行格式

一条完整的记录分为两大部分:记录的额外信息
和记录的真实数据
记录的额外信息
包括三部分: 变长字段长度列表, NULL值列表, 记录头信息
变长字段长度列表
变长字段如VARCHAR(M)
、VARBINARY(M)
、各种TEXT
类型,各种BLOB
类型,由于存储多少字节的数据是不固定的,所以在存储真实数据的时候需要把这些数据占用的字节数也存起来,这些变长字段占用的存储空间分为两部分:真实数据、真实数据占用的字节数
在Compact
行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,形成一个变长字段长度列表,各变长字段数据占用的字节数按照数据表中列的顺序逆序存放
存储真实数据占用的字节数需要占用的存储空间的计算规则:
先假设一下变量:
1. 字符集中表示一个字符最多需要使用的字节数(SHOW CHARSET
语句的结果中的Maxlen
列)为W
2. 字符集中最多能存储M
个字符,该字符集能表示的字符串最多占用的字节数就是M×W
3. 真实数据字符串占用的字节数为L
计算方法:
当M×W <= 255
,使用1个字节存储真实数据占用的字节数当
M×W > 255
,分为两种情况: 如果L <= 127
,使用1个字节存储真实数据占用的字节数,如果L > 127
,则用2个字节存储真实数据占用的字节数
注意:
1. 变长字段长度列表中不储存值为 NULL 的列内容占用的实际长度
2. 如果数据表中没有变长字段则不需要变长字段长度列表
3. 对于CHAR(M) 类型的列,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表
NULL值列表
表中的某些列可能存储NULL
值,Compact
行格式把值为NULL
的列统一管理起来,存储到NULL
值列表中,每个允许存储NULL
的列对应一个二进制位,二进制位按照表中列的顺序逆序排列,二进制位的值为1
,代表该列的值为NULL
,二进制位的值为0
,代表该列的值不为NULL
注意:
不允许存储
NULL
的列,不会加入
NULL值列表,如: 主键列、被NOT NULL
修饰的列
MySQL
规定存储NULL值列表
的最小单位是字节,如果允许存储NULL
的列数不足8位,则在字节的高位补0
记录头信息
记录头信息
由固定的5
个字节组成,5
个字节也就是40
个二进制位,不同的位代表不同的意思,如图:
记录的真实数据
记录的真实数据
除了我们自己定义的列的数据以外,MySQL
会为每条记录默认添加隐藏列
,具体如下:
| 列名 | 是否必须 | 占用空间 | 描述 |
| row_id | 否 | 6字节 | 行ID,唯一标识一条记录 |
| transaction_id | 是 | 6字节 | 事务ID |
| roll_pointer | 是 | 7字节 | 回滚指针 |
这几个列的真正名称分别是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR
InnoDB
数据表的主键生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique
键作为主键,如果表中连Unique
都没有,则InnoDB
会为表默认添加一个名为row_id
的隐藏列作为主键。InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)
注意: 如果列的值为NULL
,它们被存储在记录的额外信息的NULL值列表
处,在记录的真实数据处不再冗余存储,从而节省存储空间。
行溢出数据
VARCHAR(M)最多能存储的数据
MySQL
对一条记录占用的最大存储空间是有限制的,除了BLOB
或者TEXT
类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535
个字节
存储一个VARCHAR(M)
类型的列,其实需要占用3部分存储空间:
真实数据
真实数据占用字节的长度
NULL
值标识,如果该有NOT NULL
属性则没有这部分存储空间
如果使用的是ascii
字符集(一个字符只占用1个字节):
1. 没有NOT NULL
属性,最多存储65532
个字节的数据,真实数据的长度占用2个字节,NULL
值标识需要占用1个字节
2. 有NOT NULL
属性,最多只能存储65533
个字节的数据,真实数据的长度占用2个字节,不需要NULL
值标识
如果使用的是ascii
字符集(一个字符可能占用多个字节):
1. 没有NOT NULL
属性
gbk
字符集表示一个字符最多需要2
个字节,最多能存储32766
(65532/2)个字符
utf8
字符集表示一个字符最多需要3
个字节,最多能存储21844
(65532/3)个字符
2. 有NOT NULL
属性
gbk
字符集表示一个字符最多需要2
个字节,最多能存储32766
(65533/2)个字符
utf8
字符集表示一个字符最多需要3
个字节,最多能存储21844
(65533/3)个字符
上述情况都是在表中只有一个字段的情况下,实际上一个行中的所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节!
记录中的数据太多产生的溢出
MySQL
以页
为基本单位来管理存储空间,记录数据都会被分配到某个页
中存储,一个页的大小是16KB
(16384
字节),一个VARCHAR(M)
类型的列就最多可以存储65532
个字节,这样就可能造成一个页存放不了一条记录的尴尬情况。
在Compact
和Redundant
行格式中,如果某一列中的数据非常多的话,在本记录的真实数据处只会存储该列的前768
个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中,这个过程也叫做行溢出
,存储超出768
字节的那些页面也被称为溢出页
。简图如下:

不只是 VARCHAR(M) 类型的列,其他的 TEXT、BLOB 类型的列在存储数据非常多的时候也会发生行溢出
。
Dynamic和Compressed行格式
这俩行格式和Compact
行格式挺像,只不过在处理行溢出
数据时不同,它们不会在记录的真实数据处存储字段真实数据的前768
个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址,简图如下:

Compressed
行格式和Dynamic
不同的一点是,Compressed
行格式会采用压缩算法对页面进行压缩,以节省空间。




