我本人是一名oracle dba,对于mysql只有非核心数据库的简单维护经验。
对于oracle,文件的具体格式是内部文档才有的东西,想直接解析文件,只能先拿开源的mysql过过瘾了。
同时也是完全
出于对“数据库
”这东西本身的兴趣,决定啃一啃mysql代码,顺便看看程序通常是如何解析文件的。
要了解文件格式最好的方法是看读文件的功能代码。
入口函数:
open_table_def()
{
文件头是前64字节
if (mysql_file_read(file, head, 64, MYF(MY_NABP)))
goto err;
head[0]和head[1]是两个标志位,head[2]是frm文件的版本
如果 head[0] == (uchar) 254 && head[1] == 1
并且 (head[2] == FRM_VER || head[2] == FRM_VER+1 || (head[2] >= FRM_VER+3 && head[2] <= FRM_VER+4))
则table_type=1,表示这是一张表
(FRM_VER是frm文件的版本,如5.6.2.7的FRM_VER是6)
如果head是以字符串“TYPE=VIEW”开头,则table_type=2,表示是一个视图
如果frm描述的是一张表,就调用open_binary_frm函数,读取表结构定义。并且把head传进去继续解析。
下面看看
open_binary_frm具体是怎么做的。
最后还会建一张表,用16进制显示frm文件,看看如何逆向出表结构信息。
- open_binary_frm()
- {
- (下面省略里部分代码,保留和文件结构有关的部分)
- new_field_pack_flag= head[27];
- new_frm_ver= (head[2] - FRM_VER); // 用来计算frm文件记录的长度field_pack_length,如果new_frm_ver小于2,就是11,否则就是17。实际跟踪new_frm_ver为4
-
- // 获取frm文件中字段信息的起始位置
- if (!(pos= get_form_pos(file, head)))
- goto err;
- //seek到字段信息的起始位置
- mysql_file_seek(file,pos,MY_SEEK_SET,MYF(0));
-
- /*
- 从该位置开始读288字节,这里是字段的概要信息,如有一共有几个字段等
- */
- if (mysql_file_read(file, forminfo,288,MYF(MY_NABP)))
- goto err;
- ...
-
- /*
- 这块读取了head[33]
- 在head[33]等于5的情况下,如果share->frm_version,也就是head[2]是9,则强行改成10
- 根据注释的解释,这么做是为了让mysql5.0创建的frm里的CHAR保持CHAR,而不存储成VARCHAR,这样mysql4也能读这个frm文件
- 可能frm_version在后面会影响对CHAR和VARCHAR的解释吧
- */
- if (share->frm_version == FRM_VER_TRUE_VARCHAR -1 && head[33] == 5)
- share->frm_version= FRM_VER_TRUE_VARCHAR;
-
- /*
- head[61]是分区类型
- legacy_db_type是个枚举,将head[61]强转成legacy_db_type类型就能得到
- 可见分区类型和存储引擎类型是一回事,只不过对于非分区表,head[61]就是0
- */
- if (*(head+61) &&
- !(share->default_part_db_type=
- ha_checktype(thd, (enum legacy_db_type) (uint) *(head+61), 1, 0)))
- goto err;
-
- // head[3]是存储引擎类型
- legacy_db_type= (enum legacy_db_type) (uint) *(head+3);
-
- // head[30]-head[31]:db_create_options,不知道是啥意思
- share->db_create_options= db_create_options= uint2korr(head+30);
-
- /*
- head[51] - head[54] mysql版本
- 比如这里跟踪到50625,就是5.625
- */
- share->mysql_version= uint4korr(head+51);
-
- // 3.23的frm新加了一些统计信息的内容,以head[32]标志位判断是否读取这些内容
- if (!head[32]) // New frm file in 3.23
- {
- // head[34] - head[47]:表的平均行长
- share->avg_row_length= uint4korr(head+34);
- // head[40]:行类型
- share->row_type= (row_type) head[40];
- // head[38]和head[41]:字符集,读取顺序是head[41],head[38]
- share->table_charset= get_charset((((uint) head[41]) << 8) +
- (uint) head[38],MYF(0));
- share->null_field_first= 1;
- // head[42]:收集统计信息时采样了多少个page
- share->stats_sample_pages= uint2korr(head+42);
- // head[44]:是否自动收集统计信息
- share->stats_auto_recalc= static_cast<enum_stats_auto_recalc>(head[44]);
- }
- // head[18]-head[21]:表的最大行数
- share->max_rows= uint4korr(head+18);
- // head[22]-head[25]:表的最小行数
- share->min_rows= uint4korr(head+22);
-
- // head[28]:描述表的key的数据长度
- key_info_length= (uint) uint2korr(head+28);
- // head[6]:frm文件中,key信息的位置,实际跟踪是4096
- mysql_file_seek(file, (ulong) uint2korr(head+6), MY_SEEK_SET, MYF(0));
-
- // 读取key记录长度到buffer里
- if (read_string(file,(uchar**) &disk_buff,key_info_length))
- goto err;
-
-
- /*
- 这里可能是判断大端小端
- disk_buff是uchar数组,disk_buff[0]只有8位,而0x80是1000 0000
- if的两个分支应该是一个意思,linux走下面的分支,比较好理解
- */
- if (disk_buff[0] & 0x80)
- {
- share->keys= keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f);
- share->key_parts= key_parts= uint2korr(disk_buff+2);
- }
- else
- {
- // 有几个key
- share->keys= keys= disk_buff[0];
- // 一共有几个字段属于key
- share->key_parts= key_parts= disk_buff[1];
- }
-
-
- if (!(keyinfo = (KEY*) alloc_root(&share->mem_root,
- n_length + uint2korr(disk_buff+4))))
-
- /*
- 前6位是key的概要信息,如一共有几个key等
- 从6位以后开始解读key信息的位置
- */
- strpos=disk_buff+6;
-
- // key信息的数据长度
- n_length= keys * sizeof(KEY) + total_key_parts * sizeof(KEY_PART_INFO);
-
- /*
- disk_buff[4] - disk_buff[5]:分配存放key信息的buffer时需要多申请的字节数
- 这部分字节用来存放所有key的的名字。
- 所有key的名字是变长数据,索引拼成字符串放到后面存储
- */
- if (!(keyinfo = (KEY*) alloc_root(&share->mem_root,
- n_length + uint2korr(disk_buff+4))))
- goto err; /* purecov: inspected */
- memset(keyinfo, 0, n_length);
- share->key_info= keyinfo;
- /*
- 上面分配内存的时候,是把keys个KEY和total_key_parts个KEY_PART_INFO分配到一起了
- keyinfo是KEY指针,因此keyinfo+keys就直接跳到了keys个KEY后面的KEY_PART_INFO的位置
- key_part于是就指向了第一个KEY_PART_INFO的位置了。
- 这只是这些对象在内存里的存放顺序,不是文件里的存放顺序。
- */
- key_part= reinterpret_cast<KEY_PART_INFO*>(keyinfo+keys);
-
- if (!(rec_per_key= (ulong*) alloc_root(&share->mem_root,
- sizeof(ulong) * total_key_parts)))
- goto err;
-
-
- // 解析key
- for (i=0 ; i < keys ; i++, keyinfo++)
- {
- keyinfo->table= 0; // Updated in open_frm
- // 实际跟踪中new_frm_ver是4
- if (new_frm_ver >= 3)
- {
- // 前面strpos已经指向disk_buff+6了
- // disk_buff[6]到disk_buff[7]:40 flags:41
- keyinfo->flags= (uint) uint2korr(strpos) ^ HA_NOSAME;
- // disk_buff[8]到disk_buff[9]: key里所有字段的长度之和
- keyinfo->key_length= (uint) uint2korr(strpos+2);
- // disk_buff[10]:key有几个字段
- keyinfo->user_defined_key_parts= (uint) strpos[4];
- // disk_buff[11] key类型,包括B树,R树,哈希,全文等
- keyinfo->algorithm= (enum ha_key_alg) strpos[5];
- keyinfo->block_size= uint2korr(strpos+6);
- // 当前版本,一个key信息战8字节,跳过8字节继续解析
- strpos+=8;
- }
- else
- {
- keyinfo->flags= ((uint) strpos[0]) ^ HA_NOSAME;
- keyinfo->key_length= (uint) uint2korr(strpos+1);
- keyinfo->user_defined_key_parts= (uint) strpos[3];
- keyinfo->algorithm= HA_KEY_ALG_UNDEF;
- strpos+=4;
- }
-
- keyinfo->key_part= key_part;
- keyinfo->rec_per_key= rec_per_key;
-
- /*
- 循环key里的每个字段
- 从key_part不断++的方式可以见申请内存的时候是把所有key的keypart按顺序堆在一起的
- 当然frm文件里也是这么做的
- */
- for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++)
- {
- *rec_per_key++=0;
- //disk_buff[14~15] field number
- key_part->fieldnr= (uint16) (uint2korr(strpos) & FIELD_NR_MASK);
- /*
- disk_buff[16~16]-1 record内的偏移量,从0开始
- 数据行的第一个字节是一个标记位,然后的才是第一个字段
- */
- key_part->offset= (uint) uint2korr(strpos+2)-1;
- key_part->key_type= (uint) uint2korr(strpos+5);
-
- if (new_frm_ver >= 1)
- {
- // disk_buff[18] 是否是反向(HA_REVERSE_SORT 128)的
- key_part->key_part_flag= *(strpos+4);
- // disk_buff[21~22] 字段长度
- key_part->length= (uint) uint2korr(strpos+7);
- // 当前版本一个key_part信息占9字节,跳过9字节继续解析
- strpos+=9;
- }
- else
- {
- key_part->length= *(strpos+4);
- key_part->key_part_flag=0;
- if (key_part->length > 128)
- {
- key_part->length&=127; /* purecov: inspected */
- key_part->key_part_flag=HA_REVERSE_SORT; /* purecov: inspected */
- }
- strpos+=7;
- }
- key_part->store_length=key_part->length;
- }
- /*
- Add primary key parts if engine supports primary key extension for
- secondary keys. Here we add unique first key parts to the end of
- secondary key parts array and increase actual number of key parts.
- Note that primary key is always first if exists. Later if there is no
- primary key in the table then number of actual keys parts is set to
- user defined key parts.
- */
- keyinfo->actual_key_parts= keyinfo->user_defined_key_parts;
- keyinfo->actual_flags= keyinfo->flags;
- if (use_extended_sk && i && !(keyinfo->flags & HA_NOSAME))
- {
- const uint primary_key_parts= share->key_info->user_defined_key_parts;
- keyinfo->unused_key_parts= primary_key_parts;
- key_part+= primary_key_parts;
- rec_per_key+= primary_key_parts;
- share->key_parts+= primary_key_parts;
- }
- }
-
- /*
- 前面分配内存的时候,在n_length后面还额外分配了uint2korr(disk_buff+4)
- 下面两行是先让keynames指向这部分地址
- 此时strpos指向的是"\\377PRIMARY\\377User\\377\"形式字符串
- 下面通过strmov将strpos的字符串拷贝到keynames,并且返回keynames结尾的地址
- 所以 strmov(...) - keynames + 1 就得到了keynames的长度
- 下面两行的结果是从strpos读取字符串后,将strpos向后移动了字符串长度个byte
- */
- keynames=(char*) key_part;
- strpos+= (strmov(keynames, (char *) strpos) - keynames)+1;
-
- //reading index comments
- for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++)
- {
- if (keyinfo->flags & HA_USES_COMMENT)
- {
- keyinfo->comment.length= uint2korr(strpos);
- keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos+2,
- keyinfo->comment.length);
- strpos+= 2 + keyinfo->comment.length;
- }
- DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) ==
- (keyinfo->comment.length > 0));
- }
-
- /*
- head[16~17] record length 579,后面读文件用
- 这个类似一行记录的长度,后面用它来读由默认值组成的一行数据
- */
- share->reclength = uint2korr((head+16));
-
-
- /*
- 获取记录开始的位置
- head[6~7]是记录开始的位置
- 要加上偏移量head[14~15],如果head[14~15]是全1,就取head[47~50]
- 实际跟踪结果record_offset是4457
- */
- record_offset= (ulong) (uint2korr(head+6)+
- ((uint2korr(head+14) == 0xffff ?
- uint4korr(head+47) : uint2korr(head+14))));
-
- // head[55~58] “extra data segment”的长度
- if ((n_length= uint4korr(head+55)))
- {
- /* Read extra data segment */
- // 这个if里面是读取一些补充信息
- }
-
-
- share->key_block_size= uint2korr(head+62);
- extra_rec_buf_length= uint2korr(head+59);
- /*
- 记录长度设置为reclength加1再加extra_rec_buf_length
- 也就是head[16~17] + 1 + head[59~60]
- */
- rec_buff_length= ALIGN_SIZE(share->reclength + 1 + extra_rec_buf_length);
- share->rec_buff_length= rec_buff_length;
-
- /*
- 读取一条记录
- 前面那个读extra data segment的if里取得偏移量是record_offset+share->reclength,跳过了share->reclength
- 下面读的就是从record_offset开始的share->reclength长度的内容。
- */
- if (mysql_file_pread(file, record, (size_t) share->reclength,
- record_offset, MYF(MY_NABP)))
- goto err; /* purecov: inspected */
-
- /*
- 下面用到的pos,就是前面“pos= get_form_pos(file, head)”取到的frm里字段信息的位置,实际跟踪中pos是8192
- 跳到8192+288处
- 之所以要跳过288,时因为前面已经将这个288读到forminfo里了
- */
- mysql_file_seek(file, pos+288, MY_SEEK_SET, MYF(0));
-
- // [8192+258 ~ 8192+259] 表的列数
- share->fields= uint2korr(forminfo+258);
- // 这里有screens的概念,应该是因为这种存储方式是源自数据库出现之前的一个“报表工具”
- pos= uint2korr(forminfo+260); /* Length of all screens */
-
- // 下面取得数据是后面读取字段信息是用到的长度等信息
- n_length= uint2korr(forminfo+268);
- interval_count= uint2korr(forminfo+270);
- interval_parts= uint2korr(forminfo+272);
- int_length= uint2korr(forminfo+274);
- share->null_fields= uint2korr(forminfo+282);
- com_length= uint2korr(forminfo+284);
-
-
- if (forminfo[46] != (uchar)255)
- {
- // [8192+46] 表注释的长度
- share->comment.length= (int) (forminfo[46]);
- // [8192+47] 从这开始是表注释的字符串
- share->comment.str= strmake_root(&share->mem_root, (char*) forminfo+47,
- share->comment.length);
- }
-
- /*
- 分配字段对象的内存时用到了前面取得的几个长度
- 这里分配的是一大片内存,几个字段对象和其它一些对象将来要放在一起
- */
- if (!(field_ptr = (Field **)
- alloc_root(&share->mem_root,
- (uint) ((share->fields+1)*sizeof(Field*)+
- interval_count*sizeof(TYPELIB)+
- (share->fields+interval_parts+
- keys+3)*sizeof(char *)+
- (n_length+int_length+com_length)))))
- goto err;
-
- /*
- field_pack_length是之前根据new_frm_ver,即head[2] - FRM_VER得到的,
- 如果new_frm_ver大于等于2,field_pack_length就是17
- 后面多出来的n_length等字节数是用来都用列名拼成的字符串
- */
- read_length=(uint) (share->fields * field_pack_length +
- pos+ (uint) (n_length+int_length+com_length));
- /*
- 这里用二级指针式为了在read_string函数内部能操作disk_buff这个指针本身,而不仅仅是它指向的内容
- 这个函数执行之后,disk_buff将指向新地址,而不是原来的地址
- */
- if (read_string(file,(uchar**) &disk_buff,read_length))
- goto err; /* purecov: inspected */
-
- // strpos指向从disk_buff开始加上([8192+260 ~ 8192+261]指定的长度)的位置
- strpos= disk_buff+pos;
-
-
- // 从strpos+fields*17处 读取字段名
- memcpy((char*) names, strpos+(share->fields*field_pack_length),
- (uint) (n_length+int_length));
- comment_pos= names+(n_length+int_length);
- memcpy(comment_pos, disk_buff+read_length-com_length, com_length);
-
- // 字段名存放到share->fieldnames里
- fix_type_pointers(&interval_array, &share->fieldnames, 1, &names);
- if (share->fieldnames.count != share->fields)
- goto err;
- fix_type_pointers(&interval_array, share->intervals, interval_count,
- &names);
-
- // 逐个读取字段信息
- for (i=0 ; i < share->fields; i++, strpos+=field_pack_length, field_ptr++)
- {
- if (new_frm_ver >= 3)
- {
- /* new frm file in 4.1 */
- // strpos[3~4] 字段长度
- field_length= uint2korr(strpos+3);
- recpos= uint3korr(strpos+5);
- pack_flag= uint2korr(strpos+8);
- unireg_type= (uint) strpos[10];
- interval_nr= (uint) strpos[12];
- uint comment_length=uint2korr(strpos+15);
-
- // strpos[13] 字段类型
- field_type=(enum_field_types) (uint) strpos[13];
-
- /* charset and geometry_type share the same byte in frm */
- if (field_type == MYSQL_TYPE_GEOMETRY)
- {
- #ifdef HAVE_SPATIAL
- geom_type= (Field::geometry_type) strpos[14];
- charset= &my_charset_bin;
- #else
- error= 4; // unsupported field type
- goto err;
- #endif
- }
- else
- {
- /*
- mysql每一列支持不同的字符集
- 读取顺序是strpos[11],strpos[14]
- */
- uint csid= strpos[14] + (((uint) strpos[11]) << 8);
- if (!csid)
- charset= &my_charset_bin;
- else if (!(charset= get_charset(csid, MYF(0))))
- {
- error= 5; // Unknown or unavailable charset
- errarg= (int) csid;
- goto err;
- }
- }
- if (!comment_length)
- {
- comment.str= (char*) "";
- comment.length=0;
- }
- else
- {
- comment.str= (char*) comment_pos;
- comment.length= comment_length;
- comment_pos+= comment_length;
- }
- }
- }
举例
create table t
(
id char(21) not null,
name char(45) not null,
c1 char(34) default 'xxx',
c2 char(23) default 'yyyyy',
c3 char(35) not null,
primary key (id, name),
unique key (c1)
) engine=innodb comment="test ffffrrrrmmmm"
;
文件头(前64字节)
0000000 01fe 0c09 0003 1000 0001 3000 0000 02c2
0000010 009f 0000 0000 0000 0000 0200 003e 0008
0000020 0500 0000 0000 0008 0000 0000 0000 c200
0000030 0002 c300 00c5 1e00 0000 0000 0000 0000
[0][1][2][3]:前4个字节是fe 01 09 0c,也就是254 1 9 12。
在前两字节是254,1,并且第三字节 = FRM_VER,或者FRM_VER+1,或者(大于等FRM_VER+3企鹅小于等于FRM_VER+4),则表示这是一张表
FRM_VER是一个宏,对于mysql 5.6.27来说,FRM_VER是6
第4字节 12 表示存储引擎是innodb
[5][4]:0003 表示从第64字节开始,跳过3字节后,放的是文件中字段信息的存储位置
[7][6]:1000 = 4096,即4096,表示文件中存放表的key信息的位置
[9][8]:0001 = 1 按照[4][5]提示的位置取“key信息存储位置时”,需要取1*4=4个字节
[15][14]:02c2 : 706 在存放key信息的部分里,第706表示存放key信息的长度
[17][16]:00 9f = 159 存放一行表里的数据需要用159字节。用来从frm读取一行用默认值组成的数据。
[21][20][19][18]:0000 0000 没有设置max_rows
[25][24][23][22]:0000 0000 没有设置min_rows
[29][28]:00 3e = 62 frm文件中,一个key信息占 62
[37][36][35][34]:0000 0000 统计信息,平均行长
[41][38]:00 08 表的字符集
[40]:00 数据行的存储类型。定义在enum row_type里,只得是固定行长、变长、压缩等。
[42][43]:00 00 统计信息,采样页数。
[54][53][52][51]: 00 00 c5 c3 : 50627 数据库版本 5.6.27
[60][59]:00 00 :表里一行数据除了数据本身和1byte标记位之外,不再需要预留空间
0000040 2f2f 0000 0020 0000 0000 0000 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
[70][69][68][67]:00 00 20 00 = 8192: 从[63]开始,跳过3字节([5][4]决定),从[67]开始的4字节([9][8]决定),
8192字节开始是frm存放字段信息的位置
从4096开始
0001000 0302 0000 000d 0002 0042 0002 0000 8001
0001010 0002 0000 1540 0200 1780 0000 4000 002d
0001020 0042 0022 0001 0000 8003 0044 0000 2280
0001030 ff00 5250 4d49 5241 ff59 3163 00ff 0000
0001040 0000 0000 0000 0000 0000 0000 0000 0000
前6字节是概要信息
0302 0000 000d
[0]: 02: 这张表有2个key
[1]: 03: 这张表的key一共有3个字段
[4]: 0d = 13 : frm文件存放所有key和key part之后,还有一块区域存放key name拼成的字符串
格式类似"\\377PRIMARY\\377User\\377\"
从4096+6开始
这里开始是一个一个key,当前版本固定8字节
0002 0042 0002 0000
[3][2]: 0042 = 66: 这个key的所有字段长度之和
[4]: 02: 这个key里有2个key_part
[5]: 00: key用的什么算法,b-tree等
当前版本key信息固定8字节。后面就是它的key part,也就是字段信息
再往后9字节是第一个key的第一个key part,每个key part 9字节
8001 0002 0000 1540 xx00
[1][0]: 80 01=1000000000000001, &0011111111111111后,就得到1。意思是第一个key part是表里第一个字段
[3][2]: 00 02: 这个值-1就是字段在数据行里的偏移量(从0开始)。数据行里第一字节是标记位,因此第一个字段的偏移量是1
[8][7]: 00 15 = 21: 这个key_part的字段长度是21
再往后9字节是第一个key的第2个key part
02xx 1780 0000 4000 002d
[1][0]: 80 02: &0011111111111111后,得到2,这个key part是表里的第二个字段
[3][2]: 00 17 = : 这个字段在数据行里的偏移量是23-1=22
[8][7]: 00 2d = 45: 这个字段长度是45
再往后8个字节是第二个key
0042 0022 0001 0000
[3][2]: 0022 = 34: 这个key的所有字段长度之和是34
[4]: 01: 这个key里有1个key_part
[5]: 00: key用的什么算法,b-tree等
再往后9字节是这个key的key part
8003 0044 0000 2280 xx00
[1][0]: 80 03: &0011111111111111后,得到3,这个key part是表里的第3个字段
[3][2]: 00 44 = : 这个字段在数据行里的偏移量是68-1=67
[8][7]: 00 22 = 45: 这个字段长度是34
根据前面得到的信息,再取13字节
ffxx 5250 4d49 5241 ff59 3163 00ff
这是用key name拼的字符串,阅读顺序是
ff 50 52 49 4d 41 52 59 ff 63 31 ff 00
P R I M A R Y c 1
key的名字,一个叫PRIMARY,一个叫c1,
c1是用户定义的字段名,被自动挡做key name了
PRIMARY是数据库自己给主键起的名字
从8192开始
前288是表字段的概要信息
0002000 01d8 1000 0000 0000 0000 0000 0000 0000
0002010 0000 0000 0000 0000 0000 0000 0000 0000
0002020 0000 0000 0000 0000 0000 0000 0000 7411
0002030 7365 2074 6666 6666 7272 7272 6d6d 6d6d
0002040 0000 0000 0000 0000 0000 0000 0000 0000
*
0002100 0001 0005 0050 009e 0000 009f 0013 0000
0002110 0000 0000 0000 0050 0016 0002 0000 0000
0002120 0050 0506 1402 2029 2020 2020 2020 2020
[46]: 11 = 17: 这里不是255,表示这里是表注释的长度,17字节。
[47]... : 表的注释
74xx 7365 2074 6666 6666 7272 7272 6d6d 6d6d
正确阅读顺序是
74 65 73 74 20 66 66 66 66 72 72 72 72 6d 6d 6d 6d
t e s t 空格 f f f f r r r r m m m m
注释是"test ffffrrrrmmmm"
[269][268] 00 13 = 19: 这个和[275][274]加在一起表示所有字段名拼成的字符串长度是19
[283][282]: 02: 表里有2个字段可以为空
从8192+288开始,再空出80字节,就是具体的字段信息
当前版本一个字段信息17字节
0002170 0304 1515 0200 0000 4000 0000 fe00 0008
0002180 0500 2d05 002d 0017 0000 0040 0000 08fe
0002190 0000 0306 2222 4400 0000 8000 0000 fe00
00021a0 0008 0700 1703 0017 0066 0000 0080 0000
00021b0 08fe 0000 0308 2323 7d00 0000 4000 0000
00021c0 fe00 0008 ff00 6469 6eff 6d61 ff65 3163
00021d0 63ff ff32 3363 00ff
00021d8
第1个字段
0304 1515 0200 0000 4000 0000 fe00 0008 xx00
[4][3]: 0015 = 21: 字段长度21
[6][5]: 0002: 字段在数据行里的偏移量,规则和key_part里的偏移量一样
[11][14]: 0008: 字符集
[13]: fe=254: 字段类型是MYSQL_TYPE_STRING(来自enum_field_types)
第2个字段
05xx 2d05 002d 0017 0000 0040 0000 08fe 0000
[4][3]: 002d = 45: 字段长度45
[6][5]: 0017 = 23: 字段在数据行里的偏移量是23
[11][14]: 0008: 字符集
[13]: fe=254: 字段类型是MYSQL_TYPE_STRING(char)。如果是varchar的话,就是MYSQL_TYPE_VARCHAR
第3个字段
0306 2222 4400 0000 8000 0000 fe00 0008 xx00
...
第4个字段
07xx 1703 0017 0066 0000 0080 0000 08fe 0000
...
第5个字段
0308 2323 7d00 0000 4000 0000 fe00 0008 xx00
....
后面是所有字段的字段名拼成的字符串,用ff分割。字符串长度已经在前面的[269][268]+[275][274]规定(19字节)
ffxx 6469 6eff 6d61 ff65 3163 63ff ff32 3363 xxff
正确阅读顺序是
ff 69 64 ff 6e 61 6d 65 ff 63 31 ff 63 32 ff 63 33 ff
i d n a m e c 1 c 2 c 3
5个字段分别是id,name,c1,c2,c3
从4096+706开始
这里是由每个字段的默认值组成的一行数据,位置有前面的header决定,每个字段的偏移量从8192后面的字段信息里取得。
第1个字段
00012c0 .... 20f9 2020 2020 2020 2020 2020 2020
00012d0 2020 2020 2020 2020 .... .... .... ....
f9是行首标记位,然后就是第一个字段的默认值,全是空格(0x20)
第2个字段
00012d0 .... .... .... .... 2020 2020 2020 2020
...
第二列默认值也都是空格
第3个字段
0001300 .... .... 78.. 7878 2020 2020 2020 2020
0001310 2020 2020 2020 2020 2020 2020 2020 2020
0001320 2020 2020 2020 ..20 .... .... .... ....
第三个字段从1205开始,内容是78 78 78 ,三个小写x
第4,5个字段
0001320 .... .... .... 79.. 7979 7979 2020 2020
0001330 2020 2020 2020 2020 2020 2020 2020 2020
*
第四个字段是yyyyy,后面第五个字段又全是空格
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26239116/viewspace-1972671/,如需转载,请注明出处,否则将追究法律责任。