InnoDB行格式

InnoDB的记录按行存储在数据页中。记录在数据页种的排布在《InnoDB页面结构》中已述及,本文重点介绍InnoDB的记录格式。

1 行格式总览

InnoDB规划了26种行格式,分别对应26种动物,首字母由A至Z:Antelope, Barracuda, Cheetah, Dragon, Elk, Fox, Gazelle, Hornet, Impala, Jaguar, Kangaroo, Leopard, Moose, Nautilus, Ocelot, Porpoise, Quail, Rabbit, Shark, Tiger, Urchin, Viper, Whale, Xenops, Yak, Zebra。目前InnoDB支持的行格式只有Antelope, Barracuda。而Antelope又具体细分为RedundantCompact,Barracuda也具体细分为DynamicCompressed。创建InnoDB表时,可以通过 ROW_FORMAT=XXX子句指定行格式,例如:

Create Table t (a int, b varchar(1000) not null, c char(100), d varchar(100)) CHARSET=utf8mb3 ROW_FORMAT = COMPACT;

Redundant是MySQL 5.0之前的行格式,它存储的记录是非紧凑类型的,比较占用磁盘空间。同样的页面中存储的记录行更少,索引的效率较低。目前已很少使用。CompactDynamicCompressed三种行格式结构比较相似。由于MySQL 5.7和8.0默认的行格式为Dynamic,下面将展开介绍Dynamic行格式。

2 Dynamic格式

Dynamic行格式的级别结构如下:

变长字段长度列表 NULL值列表 记录头信息 系统列 Field 1 ... Field N

2.1 变长字段长度列表

对于Varchar、Text、Blob等这类变长的字段,其存储长度是变长的。即使对于长度相同的字段,例如CHAR(10),虽然其存储的字符是固定10个,用户输入的字符不足10个也将补齐至10个,但如果字符集是可以使用1-3个字节存储字符的utf8mb3,其存储字符的字节数也是变长的。InnoDB为了能准确划分、解析不同的字段,在每条记录的第一步部分会记录所有变长字段的长度。注意,例如Int固定长度和为空的变长字段的长度是不会记录于此的。

具体而言,每个字段的长度使用1-2字节记录。MySQL对字段由65535长度的限制也源自于此,因为2字节由16bit组成,能描述最大的数字为(2^16) - 1 = 65535。

每个字段的长度用1-2字节表示,那按什么规则区分是1个字节还是2个字节呢?在介绍规则之前先需要了解变长字段的最大可能长度的概念。变长字段的最大可能长度的计算方法为最大字符数 * 字符集最大字节数,例如上表中列b的最大字节数是b,字符集单字符最大字节数是3,那么最大可能长度为30。当变长字段的最大可能长度大于255时,用一个字节记录其长度。当变长字段的最大可能长度大于255时,使用1-2字节描述字段长度。具体使用1个字节还是2个字节,使用第一个字节的最高bit作为区分:如果其为0,表示只使用了一个字节,如果为1表示使用了2个字节。当只使用一个字节时,由于最高bit被用作标志,所以其能表示的真实长度的范围是[0, 127],当真实长度大于127时,需要使用2个字节表示。

单个页面大小只有16384字节,而InnoDB规定单个页面至少需要存放两条记录,那么一条记录最大不得超过8192字节。实际上,算上索引中FIl Header、Page Header、Page Directory、Fil Trailer的空间,那么在页面中存储的记录的长度更小。当记录超过限制大小时,会出现行溢出的现象,溢出页的格式将在第三节讨论。记录溢出时,对应变长字段的第一字节的第二个bit会对其进行标记,在变长字段长度列表处只存储留在本页面中的长度。至此,变长字段两个字节中的16个bit已经有两个bit用作标志(是否用两字节存储长度,是否有行外数据),还能用于描述字段长度的最大bit数为14,即最大能表示(2^14) - 1 = 16383字节,描述存储于当前数据页的记录长度仍然绰绰有余。

除上述规则之外,还需要注意的是变长字段长度列表的存储是按照字段的逆序存放的,与真实数据的存放的顺序相反。例如上例中的表t的变长字段b, c, d在变长字段列表中的顺序是d, c, b。

2.2 NULL值列表

为了节约空间,值为NULL的字段不会占用存储空间,而是通过NULL标记位记录。只有可能为NULL之的字段才有可能出现在NULL值列表中,如果一个表的所有列都用NOT NULL修饰,则该表所有记录都没有NULL值列表。

NULL值列表通过BITMAP来标识每个字段是否为空,每个可能为NULL的字段占一个bit位标识,如果字段为空,则为1,否则为0。与变长字段列表相似,所有的NULL值也按照字段顺序逆序排布。NULL列表占用的存储空间一定是8 bit的整数倍,即按字节为单位存储,如果可以为NULL的字段数不足8的倍数,在NULL值列表的高位补0。

2.3 记录头信息

记录头的信息在《InnoDB页面结构》中已有部分介绍,此处对其所有内容进行介绍。记录头包含的信息如下:

内容 大小 含义
预留位 1 暂未使用
预留位 1 暂未使用
delete_flag 1 是否删除的标识,如果删除为1,为多版本并发控制服务(Multi-Version Concurrency Control ,MVCC)
min_rec_flag 1 B+树非叶子结点中每一层最小的记录会添加此标识
n_owned 4 如果有Slot指向此记录,此字段会有值并定表此为组长记录,记录此Slot管理的记录数
heap_no 13 记录在页面中的物理位置(堆上的位置),每申请一块记录空间,都会为其分配一个 heap_no,从前往后编号,标记删除的记录不会减小heap_no
record_type 3 记录的类型,0表示叶子结点的用户记录,1表示非叶子结点的记录,2表示Infimum记录,3表示Supremum记录
next_record 16 下一条记录的地址,将页面内的记录串联起来

2.4 系统列

InnoDB聚簇索引可能会存在下述三个用户不可见的隐藏系统列:

列名 是否必须 占用空间 描述
DB_ROW_ID 6字节 行ID,唯一标识一条记录
DB_TRX_ID 6字节 事务ID
DB_ROLL_PTR 7字节 回滚指针
  • DB_ROW_ID:聚簇索引优先使用用户自定义的主键作为Key构建B+树,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加此隐藏列作为主键。所以此列只有在无主键并且无Unique Key的表中存在。
    此列只有在无主键表中才存在。由于用户没设置主键,InnoDB只能自己添加一个自增列作为key来构建B+树。
  • DB_TRX_ID:表示该行最新修改的事务ID,为MVCC判断记录可见性服务
  • DB_ROLL_PTR:回滚段指针,指向记录的上一个版本,同样为MVCC判断记录可见性服务,当前记录经MVCC判断不可见时,通过该指针往前回溯记录的旧版本,找到满足可见性要求的记录返回给用户

二级索引记录没有DB_TRX_ID和DB_ROLL_PTR,所以其MVCC比较麻烦。二级索引页的Page Header有MAX_TRX_ID字段,表示更新该页面的最大事务ID。如果MAX_TRX_ID小于当前事务开启时的最小事务ID,那么万事大吉,此二级索引页面中的非标记删除的二级索引记录都是可见的。否则,就需要从二级索引访问到聚簇索引,通过聚簇索引再判断记录的可见性。

2.5 用户列

用户列与列之间没有间隔,连续存放。

3 行溢出处理

3.1 行溢出时记录的格式

当变长的字段数据过长,导致索引页无法容纳两条记录,InnoDB会将过长的字段内容存储到外部存储页(blob page)。不同行格式在此处的处理略有不同。AntelopeRedundantCompact)会Field内容处存储数据内容的768字节 + 行外数据等地址指针。而Barracuda(DynamicCompressed)只在Field内容处记录行外数据等地址指针。

行外数据等地址指针占20字节,格式如下:

名称 大小 内容
BTR_EXTERN_SPACE_ID 4 外部存储页的space id
BTR_EXTERN_PAGE_NO 4 外部存储页的页码
BTR_EXTERN_OFFSET 4 外部存储页的页内偏移。
BTR_EXTERN_LEN 8 数据的总大小
  • BTR_EXTERN_OFFSET的取值分两种情况:当外部存储页不是压缩页时,该值为38。其指向外部存储页的Blob Header;当外部存储页时压缩页时,该值为12,指向Fil Header部分的FIL_PAGE_NEXT。
  • BTR_EXTERN_LEN尽管有8个字节可以存储BLOB数据的总大小,但实际上只使用了最后4个字节。这意味着在InnoDB中,单个BLOB字段的最大大小目前为4GB。

3.2 非压缩外部存储页结构

在非压缩页格式中,外部存储页的管理结构由FIl Header、Blob header、Blob data、Fil Trailer组成,溢出行中地址将指向Blob header。(关于Fil Header的介绍详见《InnoDB页面结构》)。非压缩外部存储页的结构如下:

image.png

Blob header的组成如下:

内容 大小 含义
BTR_BLOB_HDR_PART_LEN 4 当前页中存储的字段的长度
BTR_BLOB_HDR_NEXT_PAGE_NO 4 如果当前页面未能存储所有字段的全部数据,会指向下一个外部存储页面的Page no。

3.3 压缩外部存储页结构

如果外部存储页为压缩格式,其直接由Fil Header、压缩数据、Fil Trailer组成。溢出行中地址将指向Fil Header中的FIL_PAGE_NEXT(页内偏移为12)。压缩外部存储页的结构如下图所示:


image.png

4 其他行格式对比

4.2 Redundant

如前所述,Redundant是非紧凑型行格式,比较占用磁盘空间。Redundant行格式与Dynamic格式的不同之处在于并没有区分定长和变长字段,而是将所有列占用的存储空间都逆序存储在字段长度偏移列表中。并且 Redundant格式并不存在NULL值列表,使用字段长度值的第1位来判断字段是否为空,如果第1位为1,则为空。因为第1位用来记录字段是否为NULL,所以一个字节所能表示的最大长度为127。

Redundant格式的记录头占用了6个字节,分为了9部分,相较于Dynamic格式多了n_field和1byte_offs_flag字段,少了record_type字段,格式如下所示:

名称 大小 内容
预留位 1 暂未使用
预留位 1 暂未使用
delete_flag 1 是否删除的标识,如果删除为1
min_rec_flag 1 B+树非叶子结点中每一层最小的记录会添加此标识
n_owned 4 如果有slot指向此记录,此字段会有值,记录此slot管理的记录数
heap_no 13 记录在页面中的物理位置(堆上的位置),每申请一块记录空间,都会为其分配一个 heap_no,从前往后编号
n_field 10 记录中列的数量
1byte_offs_flag 1 标识字段长度偏移列表中字段的长度用1个字节还是2个字节来表示,如果所有字段长度小于127,则用一个字节表示,如果大于127,则用两个字段表示
next_record 16 下一条记录的地址,将页面内的记录串联起来

4.2 Compact

Compact是一种紧凑类型的存储格式,与Dynamic类型的存储格式基本一致。如第三节所述,作为Antelope,其溢出行的处理方式是在索引页存储变长字段的前768字节的数据+外部存储页指针,因此其变长字段长度为768+20。与Redundant格式相比,Compact行格式减少了约20%的行存储空间。

4.3 Compressed

Compressed类型与Dynamic类型拥有相同的存储特性和功能,不同之处在于使用压缩算法对页面进行压缩,包括溢出页。优点在于可以节约存储空间,但是在查找数据时需要先解压才行,会消耗更多的CPU资源。

Compressed行格式必须在建表时指定,而且需要同时指定KEY_BLOCK_SIZE。KEY_BLOCK_SIZE会控制压缩后页面的大小,指定的大小必须小于当前默认数据页的大小。如果没有指定KEY_BLOCK_SIZE,则会自动设置为默认数据页大小的一半。如果要使通用表空间包含压缩表,必须指定FILE_BLOCK_SIZE选项,如果小于当前默认数据页的大小,会自动设置为Compressed格式。其中FILE_BLOCK_SIZE的单位为Byte,KEY_BLOCK_SIZE的单位为KB。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容