Innodb物理存储结构系列2 行记录格式
前一篇讨论了Innodb system,表空间,文件的关系及数据结构,这一篇记录下Innodb行记录的格式。
前提:
1. server层和innodb层都有自己对于record的记录格式,需要进行转换。
2. 物理文件上的记录存储,需要内存中的数据结构进行对应(任何数据都需要在内存中进行处理),进行存取的转换。
1. 测试case:
create table `pp` ( `id` int(11) default null, `name1` varchar(100) default null, `name2` char(10) default null ) engine=innodb default row_format: compact insert into pp values(11,'xpchild1','xpchild2');
2. 重要的数据结构:
struct row_prebuilt_struct{ mysql_row_templ_t* mysql_template; server层的记录格式 ins_node_t* ins_node; innodb层的数据结构 } /* Insert node structure */ struct ins_node_struct{ dtuple_t* row; 写入的行row } /** Structure for an SQL data tuple of fields (logical record) */ struct dtuple_struct { dfield_t fields; row记录的fields列表 } /** Structure for an SQL data field */ struct dfield_struct{ void *data; row记录中一个字段的定义 }
2. 写入及转换过程:
1. 准备过程:
简要记录一下:
1.1: 进入并发控制:进入innodb层的线程受限制,排队进入
innodb_srv_conc_enter_innodb(prebuilt->trx);
1.2: 创建node && row元组数据结构
node = ins_node_create(INS_DIRECT, table, prebuilt->heap);
row = dtuple_create(prebuilt->heap,dict_table_get_n_cols(table));
1.3: 判断是否是compact格式: dict_table_is_comp(prebuilt->table)
2. 内存--内存之间的转换: mysql server层到innodb层
row_mysql_convert_row_to_innobase:
for (i = 0; i < prebuilt->n_template; i++)
templ = prebuilt->mysql_template + i; 从mysql层取出来temp1,
dfield = dtuple_get_nth_field(row, i); 从row中取出来dfield,
row_mysql_store_col_in_innobase_format(....); 然后用temp1去填充。
3. 内存->磁盘文件的转换:转换tuple到记录rec
先来看一下innodb存储的格式:
说明:
1. len_arr: 记录的是变长column的长度,定长的不记录。
2. null_bits: 记录哪一个column是null值,因为null的column不占用其他空间
3. extra_bytes: 一共5个字节,记录了: Deleted_flag, Min_rec_flag, N_owned, Next_recorder等信息。
4. row_id: 因为innodb使用聚簇索引表,如果定义了pk,这里就是pk的值,如果没有,innodb会自动生成一个row_id.
5. trx_id: 记录事务id
6. roll_ptr: 回滚段地址,用于回滚和mvcc
7. id, name1, name2表示表pp的字段值
3.1 分配row_id:
row_ins_alloc_row_id_step: 判断是否是pk cluster表。
如果不是,就分配一个rowid。dict_sys_get_new_row_id,
然后填充到dict_sys_write_row_id(node->row_id_buf, row_id);
3.2 生成系统index
node->index = dict_table_get_first_index(node->table);
系统自动为没有index的表,生成了一个叫GEN_CLUST_INDEX的index。本身不存在。
/* "GEN_CLUST_INDEX" is the name reserved for Innodb default system primary index. */
3.3 计算rec的长度:
extra_size = REC_N_NEW_EXTRA_BYTES + UT_BITS_IN_BYTES(index->n_nullable);
这里计算的extra_size=6,
null_bits的计算方式是:#define UT_BITS_IN_BYTES(b) (((b) + 7) / 8)
遍历每一个column,计算column的长度占用的空间,以及存储数据所需要的空间。
条件1:fixed_length不占用len_arr.
条件2:如果是变长,且定义的最大长度< =255, 则len占用1个bytes。
条件3:如果是变长,且定义的长度>255, 但实际长度<128,则len占用1个bytes。
条件4: 如果是变长,且定义的长度>255, 但实际长度>=128,或者overflow了,则len占用2个bytes
/* If the maximum length of a variable-length field is up to 255 bytes, the actual length is always stored in one byte. If the maximum length is more than 255 bytes, the actual length is stored in one byte for 0..127. The length will be encoded in two bytes when it is 128 or more, or when the field is stored externally. */ if (field->fixed_len) { ut_ad(len == field->fixed_len); /* dict_index_add_col() should guarantee this */ ut_ad(!field->prefix_len || field->fixed_len == field->prefix_len); } else if (dfield_is_ext(&fields[i])) { ut_ad(col->len >= 256 || col->mtype == DATA_BLOB); extra_size += 2; } else if (len < 128 || (col->len < 256 && col->mtype != DATA_BLOB)) { extra_size++; } else { /* For variable-length columns, we look up the maximum length from the column itself. If this is a prefix index column shorter than 256 bytes, this will waste one byte. */ extra_size += 2; }
注意:之所以对于定义>255的,可能使用1个或者2个字节,兼顾了空间使用的效率。
最终计算出来的结果是:len_arr=1; null_bits=1;
3.4 写入rec内容:
根据前面获取的size,然后把字段的内容写入的buf中,最后写入到page中。
注意: varchar 在使用的时候,并不是大于255就一定会使用2个字节存储长度。 所以varchar(>255)比小于255的多使用空间的说法,在compact格式下,是不准确的。
------------------------------------------------------------------------------------------------------------------------------------------------------
后记: 对于定义>255的column,可能使用1bytes,也可能使用2bytes来保存length,但究竟是如何存储的,读的时候又如何判断的呢?
下面来看下三个case:
1. 如果column定义>255 , actual length<128,则使用1bytes,8bit的最高为是0
2. 如果column定义>255,actual length>128 && <16384, 则使用2 bytes, 最后len |=0x80, 使最高位=1
3. 如果column定义>255, actual length >16384,则使用2 bytes, 最后len|=0xc0。 使最高两位都=1
所以,究竟是一个字节还是两个字节,在读的时候,使用最高位,如果是0,就是一个字节,如果是1,就是两个字节。
假如column是65535,最高位需要表示长度,才能够表示出来65535这么大的数? 为什么最高位没有拿来使用,而是作为标志位呢?
问题的关键就在16384, 即16k,一个page的大小是16k,所以65535需要overflow才能保存,所以,两个字节的高两位,根本不用用来保存长度,而作为标志位来使用。