redundant的存储格式为 首部是一个字段长度偏移列表(每个字段占用的字节长度及其相应的位移),同样是按照列的顺序逆序放置,当列的长度小于255字节,用1字节表示,若大于 255个字节,用2字节表>示。第二个部分为记录头信息(record header),不同与compact行格式,它的行格式固定占用6个字节,最后的部分就是实际存储的每个列的数据,NULL不占该部分任何数据,但是 char中如果有NULL值则需要占用相应的字节,另外注意,每行数据除了用户定义的列外,还有两个隐藏列,事务ID(6字节),会滚指针列(7字节), 若INNODB表没有定义,Primay key,那么每行会增加一个6字节的rowid,如果有,怎有4个字节的索引字段
问:如何理解长度偏移列表? 答:长度偏移列表表示每个目录的长度,及其相对的位置。
现在我们来做个实验来具体看下他们的区别
CREATE TABLE test1 (
t1 varchar(10) DEFAULT NULL,
t2 varchar(10) DEFAULT NULL,
t3 char(10) DEFAULT NULL,
t4 varchar(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
insert into test1 values(‘a‘,‘bb‘,‘bb‘,‘ccc‘);
insert into test1 values(‘d‘,‘ee‘,‘ee‘,‘fff‘);
insert into test1 values(‘d‘,NULL,NULL,‘fff‘);
通过python py_innodb_page_info.py -v /vobiledata/mysqldata/test/test1.ibd分析可知数据页存在00000003页上,一个页有16K,第三页通过十六进制转化为0000C000开始。
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000000, page type <Freshly Allocated Page>
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
通过hexdump -C -v /vobiledata/mysqldata/test/test1.ibd>tes1.txt分析可知
0000bff0 00 00 00 00 00 00 00 00 52 4b 47 ff 63 5e 66 c7 |........RKG.cf.|
0000c000 30 4e 95 ae 00 00 00 03 ff ff ff ff ff ff ff ff |0N..............| 0000c010 00 00 00 50 63 5f 15 76 45 bf 00 00 00 00 00 00 |...Pc_.vE.......|
0000c020 00 00 00 00 15 9c 00 02 00 ef 80 05 00 00 00 00 |................|
0000c030 00 d8 00 02 00 02 00 03 00 00 00 00 00 00 00 00 |................|
0000c040 00 00 00 00 00 00 00 00 36 01 00 00 15 9c 00 00 |........6.......|
0000c050 00 02 00 f2 00 00 15 9c 00 00 00 02 00 32 01 00 |.............2..|
0000c060 02 00 1e 69 6e 66 69 6d 75 6d 00 04 00 0b 00 00 |...infimum......|
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 00 00 10 00 |supremum........|
0000c080 2c 00 00 0c 84 58 03 00 00 00 62 81 97 80 00 00 |,....X....b.....|
0000c090 00 2d 01 10 61 62 62 62 62 20 20 20 20 20 20 20 |.-..abbbb |
0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 0c | ccc........+...|
0000c0b0 84 58 04 00 00 00 62 81 f8 80 00 00 00 2d 01 10 |.X....b......-..|
0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff|
0000c0d0 03 01 06 00 00 20 ff 98 00 00 0c 84 58 05 00 00 |..... ......X...|
0000c0e0 00 62 82 43 80 00 00 00 2d 01 10 64 66 66 66 00 |.b.C....-..dfff.|
0000c0f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0000c100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0000c110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
我们可以分析上面的数据来看INNODB是怎么存数据的。
存储数据页,在0000c078开始存数据,
03,02,01表示可变字段存储列表(记录可变字段的最长字节),
00表示记录中没有NUll(如有怎按照null的位置用二进制计算),
00 00 10 00 2c表示记录头信息(规定为5个字节长度),这个头是用来连接联系的记录,也用InnoDB默认使用紧凑(COMPACT)格式
存储数据页,在0000c078开始存数据, 03,02,01表示可变字段存储列表(记录可变字段的最长字节),
00表示记录中没有NUll(如有怎按照null的位置用二进制计算),
00 00 10 00 2c表示记录头信息(规定为5个字节长度),这个头是用来连接联系的记录,也用于行级锁。
00 00 0c 84 58 03表示rowid,我们没有设置主键,所以(隐藏的六个字节),由此可见,为了减少表空间,我们在设计表是尽量要设置主键。
00 00 00 62 81 97 六个transation ID
80 00 00 00 2d 01 10 七个字节的回滚指针
PS:null不占用空间
我们观察下第三列存的数据:03,01表示第四列和第一列不为空,06表示存在null值,为止在于第二位和第三位,00000110 = 06
接下来我们看下数据被删除后空间的变化情况:
lin_ren@test 12:19:09>select * from test1;
t1 t2 t3
t4
a bb bb
ccc
d ee ee
fff
d NULL
NULL fff
3 rows in set (0.00 sec)
xue_binbin@test 12:27:40>delete from test1 where t1 = ‘a‘;
Query OK, 1 row affected (0.00 sec)
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 20 00 10 00 |supremum.... ...|
0000c080 00 00 00 0c 84 58 00 00 00 00 63 60 0c 00 00 00 |.....X....c`....|
0000c090 00 33 26 06 61 62 62 62 62 20 20 20 20 20 20 20 |.3&.abbbb |
0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 0c | ccc........+...|
0000c0b0 84 58 01 00 00 00 62 7c 94 80 00 00 00 2d 01 1f |.X....b|.....-..|
0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff|
0000c0d0 03 01 06 00 00 20 ff 98 00 00 0c 84 58 02 00 00 |..... ......X...|
0000c0e0 00 62 7c 94 80 00 00 00 2d 01 2e 64 66 66 66 00 |.b|.....-..dfff.|
0000c0f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
我们发现数据存储没有变化
xue_binbin@test 12:35:01>insert into test1 values(‘a‘,‘bb‘,‘bb‘,‘ccc‘);
Query OK, 1 row affected (0.00 sec)
xue_binbin@test 12:35:08>select * from test1;
t1 t2 t3
t4
d ee ee
fff
d NULL
NULL fff
a bb bb
ccc
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 00 00 10 ff |supremum........|
0000c080 ef 00 00 0c 84 58 0c 00 00 00 63 63 8f 80 00 00 |.....X....cc....|
0000c090 00 2d 01 10 61 62 62 62 62 20 20 20 20 20 20 20 |.-..abbbb |
0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 0c | ccc........+...|
0000c0b0 84 58 01 00 00 00 62 7c 94 80 00 00 00 2d 01 1f |.X....b|.....-..|
0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff|
0000c0d0 03 01 06 00 00 20 ff a9 00 00 0c 84 58 02 00 00 |..... ......X...|
0000c0e0 00 62 7c 94 80 00 00 00 2d 01 2e 64 66 66 66 00 |.b|.....-..dfff.|
由此表明数据空间删除的时候不释放,在等着下一个插入数据时刻,如果插入相同的数据就可以重复利用,如果没有,就有碎片
xue_binbin@test 12:35:13>insert into test1 values(‘c‘,‘aaa‘,‘aaa‘,‘cc‘);
Query OK, 1 row affected (0.00 sec)
xue_binbin@test 12:37:24>select * from test1;
0000c070 73 75 70 72 65 6d 75 6d 03 02 01 00 00 00 10 00 |supremum........|
0000c080 77 00 00 0c 84 58 0c 00 00 00 63 63 8f 80 00 00 |w....X....cc....|
0000c090 00 2d 01 10 61 62 62 62 62 20 20 20 20 20 20 20 |.-..abbbb |
0000c0a0 20 63 63 63 03 02 01 00 00 00 18 00 2b 00 00 0c | ccc........+...|
0000c0b0 84 58 01 00 00 00 62 7c 94 80 00 00 00 2d 01 1f |.X....b|.....-..|
0000c0c0 64 65 65 65 65 20 20 20 20 20 20 20 20 66 66 66 |deeee fff|
0000c0d0 03 01 06 00 00 20 ff a9 00 00 0c 84 58 02 00 00 |..... ......X...|
0000c0e0 00 62 7c 94 80 00 00 00 2d 01 2e 64 66 66 66 02 |.b|.....-..dfff.|
0000c0f0 03 01 00 00 00 28 ff 78 00 00 0c 84 58 0d 00 00 |.....(.x....X...|
0000c100 00 63 64 b5 80 00 00 00 2d 01 10 63 61 61 61 61 |.cd.....-..caaaa|
0000c110 61 61 20 20 20 20 20 20 20 63 63 00 00 00 00 00 |aa cc.....|
由此可见,如果插入一条新的数据,则会接着往下空间存
接着我们来看下有主键的情况的INNODB存储情况:
CREATE TABLE te1 (
id int(11) NOT NULL DEFAULT ‘0‘,
t1 varchar(10) DEFAULT NULL,
t2 varchar(10) DEFAULT NULL,
t3 char(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
insert into te1 values(1,‘aa‘,‘bb‘,‘bb‘);
insert into te1 values(2,‘cc‘,NULL,NULL);
同样的分析数据: 0000c070 73 75 70 72 65 6d 75 6d 02 02 00 00 00 10 00 26 |supremum.......&|
0000c080 80 00 00 01 00 00 00 62 98 e7 80 00 00 00 2d 01 |.......b......-.|
0000c090 10 61 61 62 62 62 62 20 20 20 20 20 20 20 20 02 |.aabbbb .|
0000c0a0 06 00 00 18 ff ca 80 00 00 02 00 00 00 62 99 2e |.............b..|
0000c0b0 80 00 00 00 2d 01 10 63 63 00 00 00 00 00 00 00 |....-..cc.......|
0000c0c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
我们发现如果有主键的话,在头文件(00 00 10 00 26)会有记录索引的信息(所有的记录行80 00 00 01)01表示主键1,将比没有索引的少存一个字节
xue_binbin@test 12:44:05>update te1 set t1 =‘cc‘ where id = 1;
Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0
0000c070 73 75 70 72 65 6d 75 6d 02 02 00 00 00 10 00 22 |supremum......."|
0000c080 80 00 00 01 00 00 00 63 68 c3 00 00 00 00 33 0f |.......ch.....3.|
0000c090 a7 63 63 62 62 62 62 20 20 20 20 02 06 00 00 18 |.ccbbbb .....|
0000c0a0 ff ce 80 00 00 02 00 00 00 63 31 07 80 00 00 00 |.........c1.....|
0000c0b0 2d 01 1d 63 63 00 00 00 00 00 00 00 00 00 00 00 |-..cc...........|
由此可见,update操作是在原有的数据位置上做更改操作,不会产生碎片
冗余(redundant)
xue_binbin@test 11:00:55>create table test3 engine = innodb row_format = redundant as select * from test1;
xue_binbin@test 07:40:21>select * from test3;
t1 t2 t3
t4
a bb bb
ccc
d ee ee
fff
d NULL
NULL fff
同样分析数据:
0000c070 08 03 00 00 73 75 70 72 65 6d 75 6d 00 23 20 16 |....supremum.# .|
0000c080 14 13 0c 06 00 00 10 0f 00 ba 00 00 0c 84 58 06 |..............X.|
0000c090 00 00 00 63 40 0a 80 00 00 00 2d 01 10 61 62 62 |...c@.....-..abb|
0000c0a0 62 62 20 20 20 20 20 20 20 20 63 63 63 23 20 16 |bb ccc# .|
0000c0b0 14 13 0c 06 00 00 18 0f 00 ea 00 00 0c 84 58 07 |..............X.|
0000c0c0 00 00 00 63 40 0a 80 00 00 00 2d 01 1f 64 65 65 |...c@.....-..dee|
0000c0d0 65 65 20 20 20 20 20 20 20 20 66 66 66 21 9e 94 |ee fff!..|
0000c0e0 14 13 0c 06 00 00 20 0f 00 74 00 00 0c 84 58 08 |...... ..t....X.|
0000c0f0 00 00 00 63 40 0a 80 00 00 00 2d 01 2e 64 00 00 |...c@.....-..d..|
0000c100 00 00 00 00 00 00 00 00 66 66 66 00 00 00 00 00 |........fff.....|
0000c110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
test3表中第一行记录为(a,bb,bb,ccc)他们的长度分别为1,2,10,3
外加上还有隐藏的三个字段(rowid(长度为6),事务ID(6),回滚ID(7))
所以长度偏移列表有7个,分别为(06)6,(0c)12,(13)19,(14)20,(16)22,(20)32,(23)35
表空间数据记录为反序,则为23,20,16,14,13,0c,06
23 20 16 14 13 0c 06 长度偏移列表
00 00 10 0f 00 ba 头文件ID
00 00 0c 84 58 06 rowid
00 00 00 63 40 0a transactionID
80 00 00 00 2d 01 10 回滚ID
至于主键的话,同样的把rowid换成了4个字节的主键信息
对于插入的数据的顺序,底层是怎么插入的?
实验: create table t3(id int not null,t1 varchar(10) CHARACTER SET latin1 DEFAULT NULL,t2 varchar(10) CHARACTER SET latin1 DEFAULT NULL,t3 char(6) CHARACTER SET latin1 DEFAULT NULL,PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=compact;
lin_ren@test 05:52:24>insert into t3 values(1,‘aa‘,‘bb‘,‘cc‘);
Query OK, 1 row affected (0.00 sec)
lin_ren@test 05:53:39>insert into t3 values(3,‘aaa‘,‘bbb‘,‘ccc‘);
Query OK, 1 row affected (0.00 sec)
lin_ren@test 05:54:50>insert into t3 values(2,‘aa‘,‘bbbb‘,‘ccc‘);
Query OK, 1 row affected (0.00 sec)
0000c070 73 75 70 72 65 6d 75 6d 02 02 00 00 00 10 00 48 |supremum.......H|
0000c080 80 00 00 01 00 00 00 82 46 58 80 00 00 00 32 01 |........FX....2.|
0000c090 10 61 61 62 62 63 63 20 20 20 20 03 03 00 00 00 |.aabbcc .....|
0000c0a0 18 ff cd 80 00 00 03 00 00 00 82 46 65 80 00 00 |...........Fe...|
0000c0b0 00 32 01 10 61 61 61 62 62 62 63 63 63 20 20 20 |.2..aaabbbccc |
0000c0c0 04 02 00 00 00 20 ff db 80 00 00 02 00 00 00 82 |..... ..........|
0000c0d0 46 8a 80 00 00 00
32 01 10 61 61 62 62 62 62 63 |F.....2..aabbbbc|
0000c0e0 63 63 20 20 20 00 00 00 00 00 00 00 00 00 00 00 |cc ...........|
0000c0f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
CREATE TABLE t1 (
id int(11) NOT NULL DEFAULT ‘0‘,
t1 varchar(10) CHARACTER SET latin1 DEFAULT NULL,
t2 varchar(10) CHARACTER SET latin1 DEFAULT NULL,
t3 char(6) CHARACTER SET latin1 DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT |
lin_ren@test 05:57:17>insert into t1 values(1,‘aa‘,‘bb‘,‘cc‘);
Query OK, 1 row affected (0.00 sec)
lin_ren@test 06:03:43>insert into t1 values(3,‘a‘,‘b‘,‘c‘);
Query OK, 1 row affected (0.00 sec)
lin_ren@test 06:04:01>insert into t1 values(2,‘aa‘,‘ab‘,‘cc‘);
Query OK, 1 row affected (0.00 sec)
0000c070 08 03 00 00 73 75 70 72 65 6d 75 6d 00 1b 15 13 |....supremum....|
0000c080 11 0a 04 00 00 10 0d 00 d5 80 00 00 01 00 00 00 |................|
0000c090 82 46 d3 80 00 00 00 32 01 10 61 61 62 62 63 63 |.F.....2..aabbcc|
0000c0a0 20 20 20 20 19 13 12 11 0a 04 00 00 18 0d 00 74 | ...........t|
0000c0b0 80 00 00 03 00 00 00 82 46 d5 80 00 00 00 32 01 |........F.....2.|
0000c0c0 10 61 62 63 20 20 20 20 20 1b 15 13 11 0a 04 00 |.abc .......|
0000c0d0 00 20 0d 00 b0 80 00 00 02 00 00 00 82 46 d6 80 |. ...........F..|
0000c0e0 00 00 00 32 01 10 61 61 61 62 63 63 20 20 20 20 |...2..aaabcc [BR]]
由此可见,不管是compact,还是redundant ,用ID做主键,插入的顺序按照你的排列顺序,而不是按照id的顺序排列
optimize table t3;
lin_ren@test 10:16:09>optimize table t3;
Table Op
Msg_type Msg_text
test.t3 optimize
note Table does not support optimize, doing recreate + analyze instead
test.t3 optimize
status OK
整理后数据排序
0000c070 73 75 70 72 65 6d 75 6d 02 02 00 00 00 10 00 23 |supremum.......#|
0000c080 80 00 00 01 00 00 00 82 5f ff 80 00 00 00 32 01 |........_.....2.|
0000c090 10 61 61 62 62 63 63 20 20 20 20 04 02 00 00 00 |.aabbcc .....|
0000c0a0 18 00 25 80 00 00 02 00 00 00 82 5f ff 80 00 00 |..%........_....|
0000c0b0 00 32 01 1d 61 61 62 62 62 62 63 63 63 20 20 20 |.2..aabbbbccc |
0000c0c0 03 03 00 00 00 20 ff a8 80 00 00 03 00 00 00 82 |..... ..........|
0000c0d0 5f ff 80 00 00 00 32 01 2a 61 61 61 62 62 62 63 |_.....2.*aaabbbc|
0000c0e0 63 63 20 20 20 00 00 00 00 00 00 00 00 00 00 00 |cc ...........|
删除数据:
optimize table t3;
0000c000 6e 94 df 09 00 00 00 03 ff ff ff ff ff ff ff ff |n...............|
0000c010 00 00 00 5a 29 eb 95 68 45 bf 00 00 00 00 00 00 |...Z)..hE.......|
0000c020 00 00 00 00 16 c9 00 02 00 c0 80 04 00 00 00 00 |................|
0000c030 00 a3 00 02 00 01 00 02 00 00 00 00 00 00 00 00 |................|
0000c040 00 00 00 00 00 00 00 00 38 2f 00 00 16 c9 00 00 |........8/......|
0000c050 00 02 00 f2 00 00 16 c9 00 00 00 02 00 32 01 00 |.............2..|
0000c060 02 00 1d 69 6e 66 69 6d 75 6d 00 03 00 0b 00 00 |...infimum......|
0000c070 73 75 70 72 65 6d 75 6d 02 02 00 00 00 10 00 23 |supremum.......#|
0000c080 80 00 00 01 00 00 00 82 60 50 80 00 00 00 32 01 |........`P....2.|
0000c090 10 61 61 62 62 63 63 20 20 20 20 04 02 00 00 00 |.aabbcc .....|
0000c0a0 18 ff cd 80 00 00 02 00 00 00 82 60 50 80 00 00 |...........`P...|
0000c0b0 00 32 01 1d 61 61 62 62 62 62 63 63 63 20 20 20 |.2..aabbbbccc |
0000c0c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
由此可见,如果删除大量的数据,我们要执行optimize table t3操作来释放空间。
以上就是InnoDB行格式分两种格式(COMPACT,redundant)的区别,外加Insert,delete,update的本质区别。