之后便思考是否不管有多少个列都是NULL,该部分都只占1个字节呢? 便有了如下测试
本文约定
逻辑记录:record (元组) 物理记录:row(行) 只讨论compact行格式
所用工具
自己python写的工具innodb_extract
表内数据
| id | name | legalname | industry | province | city | size | admin_department |
+----+------+-----------+----------+----------+------+------+------------------+
| 1 | NULL | NULL | NULL | NULL | NULL | NULL | NULL |
| 2 | TOM | NULL | NULL | NULL | NULL | NULL | NULL |
| 3 | ALEX | NULL | NULL | NULL | NULL | NULL | HR |
+----+------+-----------+----------+----------+------+------+------------------+
3 rows in set (0.00 sec)
分析数据
通过工具看三行数据
# python innodb_extract.py null_test.ibd
infimum
7f 000010001c 8000000000000001 0000f1e27b17 b5000001680084
1
7e 0000180020 8000000000000002 0000f1e27b17 b5000001680094 544f4d
2 TOM
3e 000020ffb6 8000000000000003 0000f1e27b17 b50000016800a4 414c4558 4852
第一行: null标志位:0x7f (01111111) 说明:从右向左方向写,一共7个null值 record header:000010001c Transaction Id:0000f1e27b17 Roll Pointer:b5000001680084 数据:
第二行: null标志位:0x7e (01111110) 说明:除第二列,其余均是null值 record header:0000180020 Transaction Id:0000f1e27b17 Roll Pointer:b5000001680084 数据: 第二列:544f4d => TOM
假设
继续上面,如果包含Null值的字段是8个,或者9个会是怎样?
代码片段,该函数将物理记录转化为逻辑记录,版本5.5.31,源文件rem0rec.c,
结合COMPACT row格式来看:
row记录格式如下:
|---------------------extra_size-----------------------------------------|---------fields_data------------|
|--columns_lens---|---null lens----|------fixed_extrasize(5)-------------|--col1---|---col2---|---col2----|
|end<--------begin|end<-------beign|-------------------------------------|orgin---------------------------|
- 先看nulls = rec - (REC_N_NEW_EXTRA_BYTES + 1) rec为记录开始的offset,也就是,extrasize也就是固定长度的record header的长度。注意null标志位和变长字段长度列表是从右->左的方向写的(原因可参见下部分代码)。所以nulls指向的是
null lens
后一字节开始的位置。 - 再看lens = nulls - UT_BITS_IN_BYTES(index->n_nullable) index->n_nullable指的是表结构中定义can be null的字段的个数,一个字段用一个bit来标记,UT_BITS_IN_BYTES将占用bit数转为占用的字节数。所以lens指向的是column_lens后面一个字节的位置,即跳过了Null标志的占用的空间,同样在写入值的时候也是从后面向前面写。
- memset(lens + 1, 0, nulls - lens) 将nulls空间清零。
之后就是遍历每一个字段,先对定义了can be null字段进行处理
/* Store the data and the offsets */
for (i = 0, field = fields; i < n_fields; i++, field++) {
const dict_field_t* ifield;
type = dfield_get_type(field);
len = dfield_get_len(field);
if (UNIV_UNLIKELY(i == n_node_ptr_field)) {
ut_ad(dtype_get_prtype(type) & DATA_NOT_NULL);
ut_ad(len == REC_NODE_PTR_SIZE);
memcpy(end, dfield_get_data(field), len);
end += REC_NODE_PTR_SIZE;
break;
}
if (!(dtype_get_prtype(type) & DATA_NOT_NULL)) {
/* nullable field */
ut_ad(index->n_nullable > 0);
if (UNIV_UNLIKELY(!(byte) null_mask)) {
nulls--;
null_mask = 1;
}
因为方向是从右向左写,也就是从后往前写,如果该字段为null,则将null标志位设为1并向前移1位,如果满了8个,也就是有8个字段都为null则offset向左移1位,并将null_mask置为1
从这段代码看出之前的猜想,也就是并不是Null标志位只固定占用1个字节==,而是以8为单位,满8个null字段就多1个字节,不满8个也占用1个字节,高位用0补齐
这段代码是就是设置null字段与null标志位的映射关系,如果字段为null,则设置标志位为1。
栗子验证
- step 1,添加两个并设置default null
localhost.test>alter table null_test add column `kind` varchar(15) DEFAULT NULL after `size`;
Query OK, 3 rows affected (0.09 sec)
localhost.test>alter table null_test add column licenseno varchar(15) DEFAULT NULL after `kind`;
Query OK, 3 rows affected (0.11 sec)
Records: 3 Duplicates: 0 Warnings: 0.11
那么理论来讲,第一行数据有9个null列了。满8个null列之后,继续向左写移,写1个bit之后开始占据两个字节。我们通过工具解析之后看下
# python innodb_extract.py null_test.ibd
01ff 000010001d 8000000000000001 0000f1e27c81 980000028c0084
1
01fe 0000180021 8000000000000002 0000f1e27c81 980000028c0094 544f4d
2 TOM
00fe 000020ffb3 8000000000000003 0000f1e27c81 980000028c00a4 414c455848
3 ALEX HR
第一行null标志位变为0x01ff,即00000001 11111111
一共有9个null字段,满了8位之后,继续向前占1个字节从右往左继续写 同理,第二行0x01fe,即00000001 11111110
第三行0x00fe,00000000 11111110
再继续添加8个字段并设置default null
最多Null字段的第一行目前有个17个null字段,对应17个Null bit
root@hebe211 ibd]# python innodb_extract.py null_test.ibd
01ffff 000010001e 8000000000000001 0000f1e27cce c60000017600840301fffe0000
1
01fffe 0000180022 8000000000000002 0000f1e27cce c6000001760094 544f4d
2 TOM
01fefe 000020ffb0 8000000000000003 0000f1e27cce c60000017600a4 414c45 5848
3 ALEX HR
第一行null标志位变为0x01ff,即00000001 11111111 11111111
一共有17个null字段,满了两个8位之后,继续向前占1个字节从右往左继续写 同理,第二行0x01fe,即00000001 11111111 11111110
第三行0x00fe,
结论
允许null的字段需要额外的空间来保存字段Null到null标志位映射的对应关系,所以保存这个映射关系的null标志位长度并不是固定的。也就是null字段越多并不是越省空间。实际生产环境中应尽量减少can be null的字段。
作者介绍 赵晨@微博研发中心, 微博:@fiona514