时学时总结,有不全和错误还请指正!
先来看一张MySQL
内部数据结构相关的简图:
从已上图从上至下可以看到几个概念:
Tablespace:表空间
Segment:段
Extent:区
Page:页
Row:行
简短意赅的说明就是:我们写的数据保存在Row
,然后Row
又存储在Page
,Page
存储在Extent
,Extent
存储在了Segment
,最终Segment
又存储在了Tablespace
。
从大致上解读如上,如果在细一点的分析可以看到以上图还有更多的信息,如:
Tablespace
下有:Leaf node segment
-叶子段,Non-Leaf node segment
-非叶子段,Rollback segment
-回滚段。
Row
下有:Trx id
-事务ID,Roll Pointer
-回滚指针,Col1..Coln
-数据字段
除了以上的概念之外,还有很多的信息从图中无法得知。这就需要我们自己去学习和理解。而我就是尝试去写这些东西让自己能够理解这些东西。我打算从下至上的去讲述,可能并不是特别深入,好在能够有个大致的了w-数据行
我们通过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
之前用的是Redundant
,5.0
之后用的是Compact
。那么首先先讲讲Compact
。
从上图可以看到组成数据行
的基本构成。这些基本的构成怎么就可以用来存储我们的行记录
呢?请继续往下看。
Compact的记录头信息
:
记录头信息
一共占用了40bit
。这40bit
该如何充分利用呢?为了解释这些字节的作用,继续以图说明。
看如下的例子创建了一张数据表,并且插入一些数据行:
这些插入的数据是以二进制保存的,如果转成16进制部分数据如下图,这部分的数据则是从第一行数据开始的。
根据图中的解释已经可以大致了解了一行记录对应的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
不会。
格式如下截图:
记录头信息的截图如下:
从中可以得到几点信息:
n_fields(10bit):记录中列的数量,占用10bit。很好的解释了为什么行中最多有1023个列。
1byte_offs_flag:偏移列表为1B还是2B。什么意思呢?
头信息占用的是48bit。
同样以示例来解读:
将行记录的二进制转成十六进制查看如下图:
分析Redundant
和Compact
的异同:
(1)Compact
的存的是变长字段长度列表,而Redundant
存的是字段长度偏移列表。这也就意味着从header
开始查找字段值的策略也就发生了改变。但它们都是逆序存储。
(2)处理NULL值的策略不同。Compact
专门用1B
记录NULL值的列位置所在,Redundant
没有。但它是如何记录NULL值的呢?看到第三行数据的存储。
逆序偏移量列表:21 9e 94 14 13 0c 06
。从header
开始往后数06B
是RowID
,从RowID
继续往后数0x0c-0x06=0x06
即06B
则是TxId
。0x13-0x0c=0x07
即roll_pointer
,三个隐藏列则数完了。0x14-0x13=0x01
即1B的字符d
。从这突然跳到0x94
,0x9e-0x94=0x0a
即10B的NULL
值,接下来就是0x21-0x9e=0x03
的3B
fff。
Compact
对NULL值是不占用存储的,而Redundant
的char
类型还是需要占用存储空间。所以Compact
较之Redundant
算是优化了。
我们经常会用char(2)
的形式给一个字段的定长。这个长度2
在MySQL4.1之前是表示2个字节
,而之后则表示的是2个字符
。2个字符
的长度那到底是占多少字节呢?这个和MySQL的字符集相关。
latin1 :英文字符占用1B。
GBK:英文字符占用1B,中文字符占用2B。
utf8:英文字符占用1B,中文字符占用3B。
所以在某些情况下看似都是2个字符,但是占用的字节却是不同的。这也就说明了为什么在MySQL4.1之后char
类型也被视为varchar
类型,且占用的字节长度会被记录在变长字段长度列表
。值得注意的是在前面的例子中并没有发现被视为varchar
类型,那是因为前面的例子选用的字符集是latin1
。
(1)之前提到过varchar
的最大长度可以是65535
字节,但是实际上并达不到这个长度。通过测试发现最大长度只可为65532
字节(当然,这不是我做的实验,感兴趣可以自己尝试)。
(2)在一个值得注意的是65535的长度指的是字节长度,这个也和字符集相关。不同的字符集中英文字符占用的字节不同,但是总的字节长度不可超过65535
即可。
(3)较之于Oracle VARCHAR2
最大存放4000
字节,SQL Server
最大存放8000
字节,MySQL最大可存放65532
字节!然而需要注意的是这个65535
字节并不是指每个字段都可设置为65535
,而是一行记录的总长度最大为65535
。比如下图:
(4)数据页
1页大小是16KB
,即16384B
。这怎么够存入一个varchar
为36652
字节的字段呢?这里就必须提到一个概念行溢出
。行溢出
顾名思义就是:一行放不下产生溢出了。
首先看看Compact
和Redundant
对行溢出的表示:
在记录中最大存储768B
,剩下的就是用指针指向行溢出页
。
对于存储长字段的话我们会用varchar
,text
,blob
等类型。使用了长字段表示并不一定就会产生行溢出
。那么什么情况下会产生行溢出
呢?
也就是说一页中至少可以存放两条记录,否则就会产生行溢出
。经过试验发现一行的最大长度是varchar(8098)
。
InnoDB1.*
版本开始引入了新的文件格式。以前支持的Compact
和Redundant
被称之为Antelope
-羚羊。新的文件格式称为Barracuda
-梭鱼(下一个命名应该就是C开头了吧~)。不同之处在于处理行溢出
的方式不同。如下图:
而Compressed
和Dynamic
的不同之处在于Compressed
会对行溢出
进行zlib
压缩。
MySQL技术内幕(InnoDB存储引擎)第二版
评论区(0)