InnoDB从内分析之Row(一)


前言

时学时总结,有不全和错误还请指正!

先来看一张MySQL内部数据结构相关的简图:
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出
从已上图从上至下可以看到几个概念:

Tablespace:表空间

Segment:段

Extent:区

Page:页

Row:行
简短意赅的说明就是:我们写的数据保存在Row,然后Row又存储在PagePage存储在ExtentExtent存储在了Segment,最终Segment又存储在了Tablespace
从大致上解读如上,如果在细一点的分析可以看到以上图还有更多的信息,如:
Tablespace下有:Leaf node segment-叶子段,Non-Leaf node segment-非叶子段,Rollback segment-回滚段。
Row下有:Trx id-事务ID,Roll Pointer-回滚指针,Col1..Coln-数据字段
除了以上的概念之外,还有很多的信息从图中无法得知。这就需要我们自己去学习和理解。而我就是尝试去写这些东西让自己能够理解这些东西。我打算从下至上的去讲述,可能并不是特别深入,好在能够有个大致的了w-数据行

row-数据行

我们通过insert语句写入的一条数据谓之为
假设我们创建了一张数据表:

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(12) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

并且写入了一些数据:

insert into `test` set `id`=1,`name`='张三';
insert into `test` set `id`=2,`name`='李四';

这些数据理所当然的存在了MySQL的“工作目录”,而且也可以通过简单的sql语句查询得到我们要的数据。但是MySQL是如何存储和查询这些数据呢?这就需要先了解Row的数据结构。

行格式分为几种:在5.0之前用的是Redundant5.0之后用的是Compact。那么首先先讲讲Compact

Compact格式

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出
从上图可以看到组成数据行的基本构成。这些基本的构成怎么就可以用来存储我们的行记录呢?请继续往下看。

Compact的记录头信息
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出记录头信息一共占用了40bit。这40bit该如何充分利用呢?为了解释这些字节的作用,继续以图说明。
看如下的例子创建了一张数据表,并且插入一些数据行:
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出这些插入的数据是以二进制保存的,如果转成16进制部分数据如下图,这部分的数据则是从第一行数据开始的。
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出根据图中的解释已经可以大致了解了一行记录对应的Compact数据结构。

MySQL是如何知道第一行数据的起始位置呢?

从上图还可以得到一些信息:

三个隐藏的列字段:RowID(占6B),TransactionID(占6B)即TrxID,和Roll Pointer(占7B)。

变长字段03 02 01逆序:从表结构知道t1 t2 t4是变长字段,分别对应第一行记录的a bb ccc,长度分别是01 02 03。而在数据存储的时候却是以逆序存储,为什么呢?因为行记录查找过程是以记录头信息开始查找行记录的。

NULL标志位(占1B):从记录头信息往前1B就是NULL标志位。如第三行记录出现了NULL值,NULL标志位为06,对应成二进制则是0000 0110。对应1的位置表示是NULL值,也即表示2、3列两个字段是NULL值。剩下的2个字符则不是NULL值,再往前取两个字符则分别为第4、1列的长度。

next_record:下一行记录头信息的位置,类似指针指向下一条记录。通过记录头信息的字段占用说明为16bit,即00 2b,用16进制表示为0x2b。注意到当前的位置是0x81,和0x2b相加为:0xac。看向0xac这一行的字符是:2b。没错,你发现了它就是下一条记录头信息next_record。以此也就可以形成一个数据行单向链表。

(1)疑问:如果字段定义了超过8个NULL值字段,该如何存储呢?
(2)表示变长段这里是用1B,用二进制表示是:0000 0000。表示成十进制则取值范围在:0~2^8-1(255)。倘若varchar定义的变长大于255呢?则用2B表示,最大长度在0~2^16-1(65535)65535这也就是一个变长字符串最大的长度了。
(3)NULL值除了标志位占了1B,实际不存在其他占用。
(4)设置为char类型的字段在不足长度时,会以0x20占位。这也说明了在字符达不到定义的长度下char类型可能会存在空间浪费,而varchar不会。

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出

Redundant格式

格式如下截图:

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出

记录头信息的截图如下:
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出

从中可以得到几点信息:

n_fields(10bit):记录中列的数量,占用10bit。很好的解释了为什么行中最多有1023个列。

1byte_offs_flag:偏移列表为1B还是2B。什么意思呢?

头信息占用的是48bit。

同样以示例来解读:

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出将行记录的二进制转成十六进制查看如下图:

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出 从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出

分析RedundantCompact的异同:
(1)Compact的存的是变长字段长度列表,而Redundant存的是字段长度偏移列表。这也就意味着从header开始查找字段值的策略也就发生了改变。但它们都是逆序存储。
(2)处理NULL值的策略不同。Compact专门用1B记录NULL值的列位置所在,Redundant没有。但它是如何记录NULL值的呢?看到第三行数据的存储。
逆序偏移量列表:21 9e 94 14 13 0c 06。从header开始往后数06BRowID,从RowID继续往后数0x0c-0x06=0x0606B则是TxId0x13-0x0c=0x07roll_pointer,三个隐藏列则数完了。0x14-0x13=0x01即1B的字符d。从这突然跳到0x940x9e-0x94=0x0a即10B的NULL值,接下来就是0x21-0x9e=0x033Bfff。
Compact对NULL值是不占用存储的,而Redundantchar类型还是需要占用存储空间。所以Compact较之Redundant算是优化了。

字符集和char的关系

我们经常会用char(2)的形式给一个字段的定长。这个长度2在MySQL4.1之前是表示2个字节,而之后则表示的是2个字符2个字符的长度那到底是占多少字节呢?这个和MySQL的字符集相关。

latin1 :英文字符占用1B。

GBK:英文字符占用1B,中文字符占用2B。

utf8:英文字符占用1B,中文字符占用3B。
所以在某些情况下看似都是2个字符,但是占用的字节却是不同的。这也就说明了为什么在MySQL4.1之后char类型也被视为varchar类型,且占用的字节长度会被记录在变长字段长度列表。值得注意的是在前面的例子中并没有发现被视为varchar类型,那是因为前面的例子选用的字符集是latin1

varchar的最大长度

(1)之前提到过varchar的最大长度可以是65535字节,但是实际上并达不到这个长度。通过测试发现最大长度只可为65532字节(当然,这不是我做的实验,感兴趣可以自己尝试)。
(2)在一个值得注意的是65535的长度指的是字节长度,这个也和字符集相关。不同的字符集中英文字符占用的字节不同,但是总的字节长度不可超过65535即可。
(3)较之于Oracle VARCHAR2最大存放4000字节,SQL Server最大存放8000字节,MySQL最大可存放65532字节!然而需要注意的是这个65535字节并不是指每个字段都可设置为65535,而是一行记录的总长度最大为65535。比如下图:

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出(4)数据页1页大小是16KB,即16384B。这怎么够存入一个varchar36652字节的字段呢?这里就必须提到一个概念行溢出行溢出顾名思义就是:一行放不下产生溢出了。

行溢出

首先看看CompactRedundant对行溢出的表示:
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出在记录中最大存储768B,剩下的就是用指针指向行溢出页
对于存储长字段的话我们会用varchar,text,blob等类型。使用了长字段表示并不一定就会产生行溢出。那么什么情况下会产生行溢出呢?
从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出也就是说一页中至少可以存放两条记录,否则就会产生行溢出。经过试验发现一行的最大长度是varchar(8098)

Compressed和Dynamic行记录格式

InnoDB1.*版本开始引入了新的文件格式。以前支持的CompactRedundant被称之为Antelope-羚羊。新的文件格式称为Barracuda-梭鱼(下一个命名应该就是C开头了吧~)。不同之处在于处理行溢出的方式不同。如下图:

从内分析InnoDB,MySQL内部数据结构,Row 数据行,字符集和 char 的关系,行溢出CompressedDynamic的不同之处在于Compressed会对行溢出进行zlib压缩。

参考

MySQL技术内幕(InnoDB存储引擎)第二版


评论区(0)

评论