mysql存(NULL)不存_MySQL Innodb 存储结构 & 存储Null值(是否存溢出段,查询查看)...

本文介绍了MySQL InnoDB存储引擎的表空间结构,包括表空间、段、区、页和行等概念。通过实例展示了在创建和插入数据时,表空间如何增长以及NULL值如何存储。实验表明,NULL值不会占用存储空间,但会有一个标志位。测试还对比了NOT NULL和NULL字段对空间的影响,以及不同数据类型(如VARCHAR和CHAR)在存储NULL值时的区别。
摘要由CSDN通过智能技术生成

背景:

再一次看完 一书的的第4章。对前面五节的内容做又有了新的认识,顺便做下笔记。先了解下相关的概念:

表空间:INNODB 所有数据都存在表空间当中(共享表空间),要是开启innodb_file_per_table,则每张表的数据会存到单独的一个表空间内(独享表空间)。

独享表空间包括:数据,索引,插入缓存,数据字典。共享表空间包括:Undo信息(不会回收),双写缓存信息,事务信息等。

段(segment):组成表空间,有区组成。

区(extent):有64个连续的页组成。每个页16K,总共1M。对于大的数据段,每次最后可申请4个区。

页(page):是INNODB 磁盘管理的单位,有行组成。

行(row):包括事务ID,回滚指针,列信息等。

49988601_1.gifView Code

mylib.py

49988601_1.gifView Code

include.py

49988601_1.gifView Code

测试1:

root@localhost : test 02:26:13>create table tt(id int auto_increment,name varchar(10),age int,address varchar(20),primary key (id))engine=innodb;

Query OK, 0 rows affected (0.17 sec)

root@zhoujy:/var/lib/mysql/test# ls -lh tt.ibd

-rw-rw---- 1 mysql mysql 96K 2012-10-17 14:26 tt.ibd

查看ibd:

49988601_2.gif

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#python py_innodb_page_info.py /var/lib/mysql/test/tt.ibd -v

page offset 00000000, page type

page offset 00000001, page type

page offset 00000002, page type

page offset 00000003, page type , page level <0000> ---叶子节点

page offset 00000000, page type

page offset 00000000, page type

Total number of page: 6:

Freshly Allocated Page: 2

Insert Buffer Bitmap: 1

File Space Header: 1

B-tree Node: 1

File Segment inode: 1

49988601_2.gif

解释:Total number of page: 总页数

Freshly Allocated Page:可用页

Insert Buffer Bitmap:插入缓存位图页

Insert Buffer Free List:插入缓存空闲列表页

B-tree Node:数据页

Uncompressed BLOB Page:二进制大对象页,存放溢出行的页,即溢出页

上面得到的信息是表初始化大小为96K,他是有Total number of page * 16 得来的。1个数据页,2个可用页面。

root@localhost : test 02:42:58>insert into tt values(name,age,address) values('aaa',23,'HZZZ');

疑惑:为什么没有申请区?区是64个连续的页,大小1M。那么表大小也应该是至少1M。但是现在只有96K(默认)。原因是因为每个段开始的时候,先有32个页大小的碎片页存放数据,使用

完之后才是64页的连续申请,最多每次可以申请4个区,保证数据的顺序。这里看出表大小增加是按照至少64页的大小的空间来增加的,即1M增加。

验证:填充数据,写满这32个碎片页,32*16 = 512K。看看是否能申请大于1M的空间。

49988601_1.gifView Code

"额外"页:4个

page offset 00000000, page type :文件头空间页

page offset 00000001, page type :插入缓存位图页

page offset 00000002, page type :文件段节点

page offset 00000003, page type , page level <0001>:根页

碎片页:32个

page type , page level <0000>

总共36个页,ibd大小 576K的由来:32*16=512K(碎片页)+ 4*16=64(额外页),这里开始要是再插入的话,应该申请最少1M的页:

49988601_2.gif

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#ls -lh /var/lib/mysql/test/tt.ibd

-rw-rw---- 1 mysql mysql 2.0M 2012-10-17 16:10 /var/lib/mysql/test/tt.ibd

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#python py_innodb_page_info.py /var/lib/mysql/test/tt.ibd

Total number of page: 128:

Freshly Allocated Page: 91

Insert Buffer Bitmap: 1

File Space Header: 1

B-tree Node: 34

File Segment inode: 1

49988601_2.gif

页从36跳到了128,因为已经用完了32个碎片页,新的页会采用区的方式进行空间申请。信息中看到有很多可用页,正好说明这点。

▲溢出行数据存放:INNODB存储引擎是索引组织的,即每页中至少有两行记录,因此如果页中只能存放一行记录,INNODB会自动将行数据放到溢出页中。当发生溢出行的时候,实际数据保存在BLOB页中,数据页只保存数据的前768字节(老的文件格式),新的文件格式(Barracuda)采用完全行溢出的方式,数据页只保存20个字节的指针,BLOB也保存所有数据。如何查看表中有溢出行数据呢?

root@localhost : test 04:52:34>create table t1 (id int,name varchar(10),memo varchar(8000))engine =innodb default charset utf8;

Query OK, 0 rows affected (0.16 sec)

root@localhost : test 04:53:10>insert into t1 values(1,'zjy',repeat('我',8000));

Query OK, 1 row affected (0.00 sec)

查看ibd:

49988601_2.gif

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#python py_innodb_page_info.py /var/lib/mysql/test/t1.ibd -v

page offset 00000000, page type

page offset 00000001, page type

page offset 00000002, page type

page offset 00000003, page type , page level <0000>

page offset 00000004, page type

page offset 00000005, page type

Total number of page: 6:

Insert Buffer Bitmap: 1

Uncompressed BLOB Page: 2

File Space Header: 1

B-tree Node: 1

File Segment inode: 1

49988601_2.gif

从信息中看到,刚才插入的一行记录,已经溢出了,保存到了2个BLOB页中()。因为1页只有16K,又要存2行数据,所以每行记录最好小于8K,而上面的远远大于8K,所以被溢出了。当然这个也不是包括特大字段,要是一张表里面有5个字段都是varchar(512)【多个varchar的总和大于8K就可以】,也会溢出:

root@localhost : test 05:08:39>create table t2 (id int,name varchar(1000),address varchar(512),company varchar(200),xx varchar(512),memo varchar(512),dem varchar(1000))engine =innodb default charset utf8;

Query OK, 0 rows affected (0.17 sec)

root@localhost : test 05:08:43>insert into t2 values(1,repeat('周',1000),repeat('我',500),repeat('丁',500),repeat('啊',500),repeat('噢',500),repeat('阿a',500));

1000+500+500+500+500+500=3500*3>8000字节;行会被溢出:

49988601_2.gif

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#python py_innodb_page_info.py /var/lib/mysql/test/t2.ibd -v

page offset 00000000, page type

page offset 00000001, page type

page offset 00000002, page type

page offset 00000003, page type , page level <0000>

page offset 00000004, page type

page offset 00000000, page type

Total number of page: 6:

Insert Buffer Bitmap: 1

Freshly Allocated Page: 1

File Segment inode: 1

B-tree Node: 1

File Space Header: 1

Uncompressed BLOB Page: 1

49988601_2.gif

页存放真正的数据,那数据页到底存放什么?用hexdump查看:

root@zhoujy:/home/zhoujy/jiaoben/read_ibd#hexdump -C -v /var/lib/mysql/test/t1.ibd > t1.txt

查看ibd:

49988601_1.gifView Code

文本中刚好是48行,每行16字节。48*16=768字节,刚好验证了之前说的:数据页只保存数据的前768字节(老的文件格式)。

总结1:

通过上面的信息,可以能清楚的知道ibd表空间各个页的分布和利用信息以及表空间大小增加的步长;特别注意的是溢出行,一个页中至少包含2行数据,如果页中存放的行数越多,性能就越好。

************************************

************************************目的2:了解表空间如何存储数据,以及对NULL值的存储。

测试2:在测试前先了解INNODB的存储格式(row_format)。老格式(Antelope):Compact,Redumdant;新格式(Barracuda):Compressed ,Dynamic。

这里测试指针对默认的存储格式。

Compact行记录方式如下:

|变长字段长度列表(1~2字节)|NULL标志位(1字节)|记录头信息(5字节)|RowID(6字节)|事务ID(6字节)|回滚指针(7字节)|

上面信息除了"NULL标志位"[表中所有字段都定义为NOT NULL],"RowID"[表中有主键] ,"变长字段长度列表"[没有变长字段] 可能不存在外,其他信息都会出现。所以一行数据除了列数据所占用的字段外,还需要额外18字节。

一:字段全NULL

49988601_2.gif

mysql> create table mytest(t1 varchar(10),t2 varchar(10),t3 varchar(10) ,t4 varchar(10))engine=innodb charset = latin1 row_format=compact;

Query OK, 0 rows affected (0.08 sec)

mysql> insert into mytest values('a','bb','bb','ccc');

Query OK, 1 row affected (0.02 sec)

mysql> insert into mytest values('a','ee','ee','fff');

Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a',NULL,NULL,'fff');

Query OK, 1 row affected (0.00 sec)

49988601_2.gif

测试数据准备完之后,执行shell命令:

root@zhoujy:/usr/local/mysql/test#hexdump -C -v mytest.ibd > /home/zhoujy/mytest.txt

打开mytest.txt文件找到supremum这一行:

49988601_2.gif

0000c070 73 75 70 72 65 6d 75 6d 03 02 02 01 00 00 00 10 |supremum........| ----------->一行,16字节

0000c080 00 25 00 00 00 03 b9 00 00 00 00 02 49 01 82 00 |.%..........I...|

0000c090 00 01 4a 01 10 61 62 62 62 62 63 63 63 03 02 02 |..J..abbbbccc...|

0000c0a0 01 00 00 00 18 00 23 00 00 00 03 b9 01 00 00 00 |......#.........|

0000c0b0 02 49 02 83 00 00 01 4b 01 10 61 65 65 65 65 66 |.I.....K..aeeeef|

0000c0c0 66 66 03 01 06 00 00 20 ff a6 00 00 00 03 b9 02 |ff..... ........|

0000c0d0 00 00 00 02 49 03 84 00 00 01 4c 01 10 61 66 66 |....I.....L..aff|

0000c0e0 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |f...............|

49988601_2.gif

解释:

第一行数据:

03 02 02 01 /*变长字段*/ ----表中4个字段类型为varchar,并且没有NULL数据,而且每个字段君小于255。

00 /*NULL标志位,第一行没有null的数据*/

00 00 10 00 25 /*记录头信息,固定5个字节*/

00 00 00 03 b9 00 /*RowID,固定6个字节,表没有主键*/

00 00 00 02 49 01 /*事务ID,固定6个字节*/

82 0000 01 4a 01 10 /*回滚指针,固定7个字节*/

61 62 62 62 62 63 63 63 /*列的数据*/

第二行数据和第一行数据一样(颜色匹配)。

第三行数据(有NULL值)和第一行的解释的颜色对应起来比较差别:

03 02 02 01 VS 03 01----------当值为NULL时,变长字段列表不会占用存储空间。

61 62 62 62 62 63 63 63 VS 61 66 66 66--------- NULL值没有存储,不占空间

结论:当值为NULL时,变长字段列表不会占用存储空间。NULL值没有存储,不占空间,但是需要一个标志位(一行一个)。

二:字段全NOT NULL

49988601_2.gif

mysql> create table mytest(t1 varchar(10) NOT NULL,t2 varchar(10) NOT NULL,t3 varchar(10) NOT NULL,t4 varchar(10) NOT NULL)engine=innodb charset = latin1 row_format=compact;

Query OK, 0 rows affected (0.03 sec)

mysql> insert into mytest values('a','bb','bb','ccc');

Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a','ee','ee','fff');

Query OK, 1 row affected (0.01 sec)

mysql> insert into mytest values('a',NULL,NULL,'fff');

ERROR 1048 (23000): Column 't2' cannot be null

49988601_2.gif

步骤和上面一样,得到的ibd的结果是:

0000c070 73 75 70 72 65 6d 75 6d 03 02 02 01 00 00 10 00 |supremum........|

0000c080 24 00 00 00 03 b9 03 00 00 00 02 49 07 87 00 00 |$..........I....|

0000c090 01 4f 01 10 61 62 62 62 62 63 63 63 03 02 02 01 |.O..abbbbccc....|

0000c0a0 00 00 18 ff cb 00 00 00 03 b9 04 00 00 00 02 49 |...............I|

0000c0b0 08 88 00 00 01 50 01 10 61 65 65 65 65 66 66 66 |.....P..aeeeefff|

和上面比较,发现少了NULL的标志位信息。

结论:NULL值会有额外的空间来存储,即每行1字节的大小。对于相同数据的表,字段中有NULL值的表比NOT NULL的大。

三:1个NULL,和1个''的数据:

49988601_2.gif

mysql> create table mytest(t1 varchar(10) NOT NULL,t2 varchar(10) NOT NULL DEFAULT '',t3 varchar(10) NOT NULL ,t4 varchar(10))engine=innodb charset = latin1 row_format=compact;

Query OK, 0 rows affected (0.02 sec)

mysql> insert into mytest(t1,t2) values('A','BB');

Query OK, 1 row affected, 1 warning (0.01 sec)

49988601_2.gif

步骤和上面一样,得到的ibd的结果是:

0000c070 73 75 70 72 65 6d 75 6d 00 02 01 01 00 00 10 ff |supremum........|

0000c080 ef00 00 00 43 b9 03 00 00 00 02 4a 15 90 00 00 |....C......J....|

0000c090 01 c2 01 10 41 42 42 00 00 00 00 00 00 00 00 00 |....ABB.........|

和上面2个区别主要在于变长列表和列数据这里。

结论:列数据信息里表明了 NULL数据和''数据都不占用任何空间,对于变长字段列表的信息,和一对比得出:‘’数据虽然不需要占用任何存储空间,但是在变长字段列表里面还是需要占用一个字节,NULL值不需要占用”,只是NULL会有额外的一个标志位,所以能有个优化的说法:“数据库表中能设置NOT NULL的就尽量设置为NOT NULL,除非确实需要NULL值得。” 在此得到了证明。

上面的测试都是针对VARCHAR的变长类型,那对于CHAR呢?CHAR 测试:

49988601_2.gif

root@localhost : test 10:33:35>create table mytest(t1 char(10),t2 char(10),t3 char(10) ,t4 char(10))engine=innodb charset = latin1 row_format=compact;Query OK, 0 rows affected (0.16 sec)

root@localhost : test 10:33:59>insert into mytest values('a','bb','bb','ccc');

Query OK, 1 row affected (0.00 sec)

root@localhost : test 10:34:09>insert into mytest values('a','ee','ee','fff');

Query OK, 1 row affected (0.00 sec)

root@localhost : test 10:34:19>insert into mytest values('a',NULL,NULL,'fff');

Query OK, 1 row affected (0.00 sec)

49988601_2.gif

打开ibd生成的文件:

49988601_2.gif

0000c060 02 00 1b 69 6e 66 69 6d 75 6d 00 04 00 0b 00 00 |...infimum......|

0000c070 73 75 70 72 65 6d 75 6d 00 00 00 10 00 41 00 00 |supremum.....A..|

0000c080 00 0a f5 00 00 00 00 81 2d 07 80 00 00 00 32 01 |........-.....2.|

0000c090 10 61 20 20 20 20 20 20 20 20 20 62 62 20 20 20 |.a bb |

0000c0a0 20 20 20 20 20 62 62 20 20 20 20 20 20 20 20 63 | bb c|

0000c0b0 63 63 20 20 20 20 20 20 20 00 00 00 18 00 41 00 |cc .....A.|

0000c0c0 00 00 0a f5 01 00 00 00 81 2d 08 80 00 00 00 32 |.........-.....2|

0000c0d0 01 10 61 20 20 20 20 20 20 20 20 20 65 65 20 20 |..a ee |

0000c0e0 20 20 20 20 20 20 65 65 20 20 20 20 20 20 20 20 | ee |

0000c0f0 66 66 66 20 20 20 20 20 20 20 06 00 00 20 ff 70 |fff ... .p|

0000c100 00 00 00 0a f5 02 00 00 00 81 2d 09 80 00 00 00 |..........-.....|

0000c110 32 01 10 61 20 20 20 20 20 20 20 20 20 66 66 66 |2..a fff|

0000c120 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00 00 | .........|

49988601_2.gif

和一的varchar比较发现:少了变长字段列表,但是对于char来讲,需要固定长度来存储的,存不到固定长度,也会被填充满。如:20;并且NULL值也不需要占用存储空间。

混合(varchar,char):

49988601_2.gif

root@localhost : test 11:21:48>create table mytest(t1 int,t2 char(10),t3 varchar(10) ,t4 char(10))engine=innodb charset = latin1 row_format=compact;

Query OK, 0 rows affected (0.17 sec)

root@localhost : test 11:21:50>insert into mytest values(1,'a','b','c');

Query OK, 1 row affected (0.00 sec)

root@localhost : test 11:22:06>insert into mytest values(11,'aa','bb','cc');

Query OK, 1 row affected (0.00 sec)

49988601_2.gif

从上面的表结构中看出:

1,变长字段列表长度:1

2,NULL标志位:1

3,记录头信息:5

4,RowID:6

5,事务ID:6

6,回滚指针:7

idb的信息:

49988601_2.gif

0000c070 73 75 70 72 65 6d 75 6d 01 00 00 00 10 00 33 00 |supremum......3.|

0000c080 00 00 0a f5 07 00 00 00 81 2d 1a 80 00 00 00 32 |.........-.....2|

0000c090 01 10 80 00 00 01 61 20 20 20 20 20 20 20 20 20 |......a |

0000c0a0 62 63 20 20 20 20 20 20 20 20 20 02 00 00 00 18 |bc .....|

0000c0b0 ff be 00 00 00 0a f5 08 00 00 00 81 2d 1b 80 00 |............-...|

0000c0c0 00 00 32 01 10 80 00 00 0b 61 61 20 20 20 20 20 |..2......aa |

0000c0d0 20 20 20 62 62 63 63 20 20 20 20 20 20 20 20 00 | bbcc .|

49988601_2.gif

从上信息得出和之前预料的一样:因为表中只有一个varchar字段,所以,变长列表长度就只有:01

特别注意的是:各个列数据存储的信息:t1字段为int 类型,占用4个字节的大小。第一行:80 00 00 01 就是表示 1 数字;第二行:80 00 00   0b 表示了11的数字。[select hex(11)  == B ],其他的和上面的例子一样。

上面都是latin1单字节字符集的说明,那对于多字节字符集的情况怎么样?

49988601_2.gif

root@localhost : test 11:52:10>create table mytest(id int auto_increment,t2 varchar(10),t3 varchar(10) ,t4 char(10),primary key(id))engine=innodb charset = utf8 row_format=compact;

Query OK, 0 rows affected (0.17 sec)

root@localhost : test 11:52:11>insert into mytest(t2,t3,t4) values('bb','bb','ccc');

Query OK, 1 row affected (0.00 sec)

root@localhost : test 11:55:34>insert into mytest(t2,t3,t4) values('我们','他们','我们的');

Query OK, 1 row affected (0.00 sec)

49988601_2.gif

ibd信息如下:

0000c070 73 75 70 72 65 6d 75 6d 0a 02 02 00 00 00 10 00 |supremum........|

0000c080 28 80 00 00 01 00 00 00 81 2d 27 80 00 00 00 32 |(........-'....2|

0000c090 01 10 62 62 62 62 63 63 63 20 20 20 20 20 20 20 |..bbbbccc |

0000c0a0 0a 06 06 00 00 00 18 ff c7 80 00 00 02 00 00 00 |................|

0000c0b0 81 2d 28 80 00 00 00 32 01 10 e6 88 91 e4 bb ac |.-(....2........|

0000c0c0 e4 bb96 e4 bb ac e6 88 91 e4 bb ac e7 9a 84 20 |............... |

因为表有了主键,所以ROWID(6字节)不见了。

特别注意的是:变长字段列表是3?表里面的varchar类型的列只有2个啊。经测试得出:在多字节字符集的条件下,char类型被当成可变长度的类型来处理,他们的行存储基本没有区别,所以这个就出现变长列表是3了,因为是utf8字符集,占用三个字节。所以一个汉字均占用了一个页中3个字节的空间(”我们“ :e6 88 91 e4 bb ac)。

数据列的信息:

id列的1值,应该是80 00 00 01,为什么这个显示00 32 01 10,而且所有的id都是00 32 01 10。测试发现,id为自增主键的时候,id的4个字节长度都是以00 32 01 10表示。否则和前面一个例子里说的,用select HEX(X) 表示。

总结2:

上面的测试都是基于COMPACT存储格式的,不管是varchar还是char,NULL值是不需要占用存储空间的;特别需要注意的是Redumdant的记录头信息需要6个固定字节,而NULL值对于varchar来说是不需要占用存储空间,对于char来说将会占用最大值的字节数;在多字节字符集的条件下,CHAR和VARCHAR的行存储基本是没有区别的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值