mysql程式碼閱讀-frm檔案格式解析

liiinuuux發表於2016-01-06
我本人是一名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檔案,看看如何逆向出表結構
資訊。

點選(此處)摺疊或開啟

  1. open_binary_frm()
  2. {
  3. (下面省略裡部分程式碼,保留和檔案結構有關的部分)
  4.   new_field_pack_flag= head[27];
  5.   new_frm_ver= (head[2] - FRM_VER); // 用來計算frm檔案記錄的長度field_pack_length,如果new_frm_ver小於2,就是11,否則就是17。實際跟蹤new_frm_ver為4

  6.   // 獲取frm檔案中欄位資訊的起始位置
  7.   if (!(pos= get_form_pos(file, head)))
  8.         goto err;
  9.     //seek到欄位資訊的起始位置
  10.     mysql_file_seek(file,pos,MY_SEEK_SET,MYF(0));

  11.     /*
  12.     從該位置開始讀288位元組,這裡是欄位的概要資訊,如有一共有幾個欄位等
  13.     */
  14.     if (mysql_file_read(file, forminfo,288,MYF(MY_NABP)))
  15.         goto err;
  16.     ...

  17.     /*
  18.     這塊讀取了head[33]
  19.     在head[33]等於5的情況下,如果share->frm_version,也就是head[2]是9,則強行改成10
  20.     根據註釋的解釋,這麼做是為了讓mysql5.0建立的frm裡的CHAR保持CHAR,而不儲存成VARCHAR,這樣mysql4也能讀這個frm檔案
  21.     可能frm_version在後面會影響對CHAR和VARCHAR的解釋吧
  22.     */
  23.     if (share->frm_version == FRM_VER_TRUE_VARCHAR -1 && head[33] == 5)
  24.         share->frm_version= FRM_VER_TRUE_VARCHAR;

  25.     /*
  26.     head[61]是分割槽型別
  27.     legacy_db_type是個列舉,將head[61]強轉成legacy_db_type型別就能得到
  28.     可見分割槽型別和儲存引擎型別是一回事,只不過對於非分割槽表,head[61]就是0
  29.     */
  30.     if (*(head+61) &&
  31.       !(share->default_part_db_type=
  32.         ha_checktype(thd, (enum legacy_db_type) (uint) *(head+61), 1, 0)))
  33.     goto err;

  34.     // head[3]是儲存引擎型別
  35.     legacy_db_type= (enum legacy_db_type) (uint) *(head+3);

  36.     // head[30]-head[31]:db_create_options,不知道是啥意思
  37.     share->db_create_options= db_create_options= uint2korr(head+30);

  38.     /*
  39.     head[51] - head[54] mysql版本
  40.     比如這裡跟蹤到50625,就是5.625
  41.     */
  42.     share->mysql_version= uint4korr(head+51);

  43.     // 3.23的frm新加了一些統計資訊的內容,以head[32]標誌位判斷是否讀取這些內容
  44.     if (!head[32]) // New frm file in 3.23
  45.     {
  46.         // head[34] - head[47]:表的平均行長
  47.         share->avg_row_length= uint4korr(head+34);
  48.         // head[40]:行型別
  49.         share->row_type= (row_type) head[40];
  50.         // head[38]和head[41]:字符集,讀取順序是head[41],head[38]
  51.         share->table_charset= get_charset((((uint) head[41]) << 8) +
  52.                                             (uint) head[38],MYF(0));
  53.         share->null_field_first= 1;
  54.         // head[42]:收集統計資訊時取樣了多少個page
  55.         share->stats_sample_pages= uint2korr(head+42);
  56.         // head[44]:是否自動收集統計資訊
  57.         share->stats_auto_recalc= static_cast<enum_stats_auto_recalc>(head[44]);
  58.     }
  59.     // head[18]-head[21]:表的最大行數
  60.     share->max_rows= uint4korr(head+18);
  61.     // head[22]-head[25]:表的最小行數
  62.     share->min_rows= uint4korr(head+22);

  63.     // head[28]:描述表的key的資料長度
  64.     key_info_length= (uint) uint2korr(head+28);
  65.     // head[6]:frm檔案中,key資訊的位置,實際跟蹤是4096
  66.     mysql_file_seek(file, (ulong) uint2korr(head+6), MY_SEEK_SET, MYF(0));

  67.     // 讀取key記錄長度到buffer裡
  68.     if (read_string(file,(uchar**) &disk_buff,key_info_length))
  69.     goto err;


  70.     /*
  71.     這裡可能是判斷大端小端
  72.     disk_buff是uchar陣列,disk_buff[0]只有8位,而0x80是1000 0000
  73.     if的兩個分支應該是一個意思,linux走下面的分支,比較好理解
  74.     */
  75.     if (disk_buff[0] & 0x80)
  76.     {
  77.         share->keys= keys= (disk_buff[1] << 7) | (disk_buff[0] & 0x7f);
  78.         share->key_parts= key_parts= uint2korr(disk_buff+2);
  79.     }
  80.     else
  81.     {
  82.         // 有幾個key
  83.         share->keys= keys= disk_buff[0];
  84.         // 一共有幾個欄位屬於key
  85.         share->key_parts= key_parts= disk_buff[1];
  86.     }


  87.     if (!(keyinfo = (KEY*) alloc_root(&share->mem_root,
  88.                     n_length + uint2korr(disk_buff+4))))

  89.     /*
  90.     前6位是key的概要資訊,如一共有幾個key等
  91.     從6位以後開始解讀key資訊的位置
  92.     */
  93.     strpos=disk_buff+6;

  94.     // key資訊的資料長度
  95.     n_length= keys * sizeof(KEY) + total_key_parts * sizeof(KEY_PART_INFO);

  96.     /*
  97.     disk_buff[4] - disk_buff[5]:分配存放key資訊的buffer時需要多申請的位元組數
  98.     這部分位元組用來存放所有key的的名字。
  99.     所有key的名字是變長資料,索引拼成字串放到後面儲存
  100.     */
  101.     if (!(keyinfo = (KEY*) alloc_root(&share->mem_root,
  102.                     n_length + uint2korr(disk_buff+4))))
  103.     goto err; /* purecov: inspected */
  104.     memset(keyinfo, 0, n_length);
  105.     share->key_info= keyinfo;
  106.     /*
  107.     上面分配記憶體的時候,是把keys個KEY和total_key_parts個KEY_PART_INFO分配到一起了
  108.     keyinfo是KEY指標,因此keyinfo+keys就直接跳到了keys個KEY後面的KEY_PART_INFO的位置
  109.     key_part於是就指向了第一個KEY_PART_INFO的位置了。
  110.     這只是這些物件在記憶體裡的存放順序,不是檔案裡的存放順序。
  111.     */
  112.     key_part= reinterpret_cast<KEY_PART_INFO*>(keyinfo+keys);

  113.     if (!(rec_per_key= (ulong*) alloc_root(&share->mem_root,
  114.                                          sizeof(ulong) * total_key_parts)))
  115.     goto err;


  116.     // 解析key
  117.     for (i=0 ; i < keys ; i++, keyinfo++)
  118.     {
  119.         keyinfo->table= 0; // Updated in open_frm
  120.         // 實際跟蹤中new_frm_ver是4
  121.         if (new_frm_ver >= 3)
  122.         {
  123.           // 前面strpos已經指向disk_buff+6了
  124.           // disk_buff[6]到disk_buff[7]:40 flags:41
  125.           keyinfo->flags= (uint) uint2korr(strpos) ^ HA_NOSAME;
  126.           // disk_buff[8]到disk_buff[9]: key裡所有欄位的長度之和
  127.           keyinfo->key_length= (uint) uint2korr(strpos+2);
  128.           // disk_buff[10]:key有幾個欄位
  129.           keyinfo->user_defined_key_parts= (uint) strpos[4];
  130.           // disk_buff[11] key型別,包括B樹,R樹,雜湊,全文等
  131.           keyinfo->algorithm= (enum ha_key_alg) strpos[5];
  132.           keyinfo->block_size= uint2korr(strpos+6);
  133.           // 當前版本,一個key資訊戰8位元組,跳過8位元組繼續解析
  134.           strpos+=8;
  135.         }
  136.         else
  137.         {
  138.           keyinfo->flags= ((uint) strpos[0]) ^ HA_NOSAME;
  139.           keyinfo->key_length= (uint) uint2korr(strpos+1);
  140.           keyinfo->user_defined_key_parts= (uint) strpos[3];
  141.           keyinfo->algorithm= HA_KEY_ALG_UNDEF;
  142.           strpos+=4;
  143.         }

  144.         keyinfo->key_part= key_part;
  145.         keyinfo->rec_per_key= rec_per_key;

  146.         /*
  147.         迴圈key裡的每個欄位
  148.         從key_part不斷++的方式可以見申請記憶體的時候是把所有key的keypart按順序堆在一起的
  149.         當然frm檔案裡也是這麼做的
  150.         */
  151.         for (j=keyinfo->user_defined_key_parts ; j-- ; key_part++)
  152.         {
  153.           *rec_per_key++=0;
  154.           //disk_buff[14~15] field number
  155.           key_part->fieldnr= (uint16) (uint2korr(strpos) & FIELD_NR_MASK);
  156.           /*
  157.           disk_buff[16~16]-1 record內的偏移量,從0開始
  158.           資料行的第一個位元組是一個標記位,然後的才是第一個欄位
  159.           */
  160.           key_part->offset= (uint) uint2korr(strpos+2)-1;
  161.           key_part->key_type= (uint) uint2korr(strpos+5);

  162.           if (new_frm_ver >= 1)
  163.           {
  164.              // disk_buff[18] 是否是反向(HA_REVERSE_SORT 128)
  165.              key_part->key_part_flag= *(strpos+4);
  166.              // disk_buff[21~22] 欄位長度
  167.              key_part->length= (uint) uint2korr(strpos+7);
  168.              // 當前版本一個key_part資訊佔9位元組,跳過9位元組繼續解析
  169.              strpos+=9;
  170.           }
  171.           else
  172.           {
  173.         key_part->length= *(strpos+4);
  174.         key_part->key_part_flag=0;
  175.         if (key_part->length > 128)
  176.         {
  177.           key_part->length&=127; /* purecov: inspected */
  178.           key_part->key_part_flag=HA_REVERSE_SORT; /* purecov: inspected */
  179.         }
  180.         strpos+=7;
  181.           }
  182.           key_part->store_length=key_part->length;
  183.         }
  184.         /*
  185.           Add primary key parts if engine supports primary key extension for
  186.           secondary keys. Here we add unique first key parts to the end of
  187.           secondary key parts array and increase actual number of key parts.
  188.           Note that primary key is always first if exists. Later if there is no
  189.           primary key in the table then number of actual keys parts is set to
  190.           user defined key parts.
  191.         */
  192.         keyinfo->actual_key_parts= keyinfo->user_defined_key_parts;
  193.         keyinfo->actual_flags= keyinfo->flags;
  194.         if (use_extended_sk && i && !(keyinfo->flags & HA_NOSAME))
  195.         {
  196.           const uint primary_key_parts= share->key_info->user_defined_key_parts;
  197.           keyinfo->unused_key_parts= primary_key_parts;
  198.           key_part+= primary_key_parts;
  199.           rec_per_key+= primary_key_parts;
  200.           share->key_parts+= primary_key_parts;
  201.         }
  202.     }

  203.     /*
  204.     前面分配記憶體的時候,在n_length後面還額外分配了uint2korr(disk_buff+4)
  205.     下面兩行是先讓keynames指向這部分地址
  206.     此時strpos指向的是"\\377PRIMARY\\377User\\377\"形式字串
  207.     下面透過strmov將strpos的字串複製到keynames,並且返回keynames結尾的地址
  208.     所以 strmov(...) - keynames + 1 就得到了keynames的長度
  209.     下面兩行的結果是從strpos讀取字串後,將strpos向後移動了字串長度個byte
  210.     */
  211.     keynames=(char*) key_part;
  212.     strpos+= (strmov(keynames, (char *) strpos) - keynames)+1;

  213.     //reading index comments
  214.     for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++)
  215.     {
  216.       if (keyinfo->flags & HA_USES_COMMENT)
  217.       {
  218.         keyinfo->comment.length= uint2korr(strpos);
  219.         keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos+2,
  220.                                            keyinfo->comment.length);
  221.         strpos+= 2 + keyinfo->comment.length;
  222.       }
  223.       DBUG_ASSERT(MY_TEST(keyinfo->flags & HA_USES_COMMENT) ==
  224.                  (keyinfo->comment.length > 0));
  225.    }

  226.   /*
  227.   head[16~17] record length 579,後面讀檔案用
  228.   這個類似一行記錄的長度,後面用它來讀由預設值組成的一行資料
  229.   */
  230.   share->reclength = uint2korr((head+16));


  231.   /*
  232.   獲取記錄開始的位置
  233.   head[6~7]是記錄開始的位置
  234.   要加上偏移量head[14~15],如果head[14~15]是全1,就取head[47~50]
  235.   實際跟蹤結果record_offset是4457
  236.   */
  237.   record_offset= (ulong) (uint2korr(head+6)+
  238.                           ((uint2korr(head+14) == 0xffff ?
  239.                             uint4korr(head+47) : uint2korr(head+14))));

  240.   // head[55~58] “extra data segment”的長度
  241.   if ((n_length= uint4korr(head+55)))
  242.   {
  243.     /* Read extra data segment */
  244.     // 這個if裡面是讀取一些補充資訊
  245.   }


  246.   share->key_block_size= uint2korr(head+62);
  247.   extra_rec_buf_length= uint2korr(head+59);
  248.   /*
  249.   記錄長度設定為reclength加1再加extra_rec_buf_length
  250.   也就是head[16~17] + 1 + head[59~60]
  251.   */
  252.   rec_buff_length= ALIGN_SIZE(share->reclength + 1 + extra_rec_buf_length);
  253.   share->rec_buff_length= rec_buff_length;

  254.   /*
  255.   讀取一條記錄
  256.   前面那個讀extra data segment的if裡取得偏移量是record_offset+share->reclength,跳過了share->reclength
  257.   下面讀的就是從record_offset開始的share->reclength長度的內容。
  258.   */
  259.   if (mysql_file_pread(file, record, (size_t) share->reclength,
  260.                        record_offset, MYF(MY_NABP)))
  261.     goto err; /* purecov: inspected */

  262.   /*
  263.   下面用到的pos,就是前面“pos= get_form_pos(file, head)”取到的frm裡欄位資訊的位置,實際跟蹤中pos是8192
  264.   跳到8192+288處
  265.   之所以要跳過288,時因為前面已經將這個288讀到forminfo裡了
  266.   */
  267.   mysql_file_seek(file, pos+288, MY_SEEK_SET, MYF(0));

  268.   // [8192+258 ~ 8192+259] 表的列數
  269.   share->fields= uint2korr(forminfo+258);
  270.   // 這裡有screens的概念,應該是因為這種儲存方式是源自資料庫出現之前的一個“報表工具”
  271.   pos= uint2korr(forminfo+260); /* Length of all screens */

  272.   // 下面取得資料是後面讀取欄位資訊是用到的長度等資訊
  273.   n_length= uint2korr(forminfo+268);
  274.   interval_count= uint2korr(forminfo+270);
  275.   interval_parts= uint2korr(forminfo+272);
  276.   int_length= uint2korr(forminfo+274);
  277.   share->null_fields= uint2korr(forminfo+282);
  278.   com_length= uint2korr(forminfo+284);


  279.   if (forminfo[46] != (uchar)255)
  280.   {
  281.     // [8192+46] 表註釋的長度
  282.     share->comment.length= (int) (forminfo[46]);
  283.     // [8192+47] 從這開始是表註釋的字串
  284.     share->comment.str= strmake_root(&share->mem_root, (char*) forminfo+47,
  285.                                      share->comment.length);
  286.   }

  287.   /*
  288.   分配欄位物件的記憶體時用到了前面取得的幾個長度
  289.   這裡分配的是一大片記憶體,幾個欄位物件和其它一些物件將來要放在一起
  290.   */
  291.   if (!(field_ptr = (Field **)
  292.   alloc_root(&share->mem_root,
  293.        (uint) ((share->fields+1)*sizeof(Field*)+
  294.          interval_count*sizeof(TYPELIB)+
  295.          (share->fields+interval_parts+
  296.           keys+3)*sizeof(char *)+
  297.          (n_length+int_length+com_length)))))
  298.     goto err;

  299.   /*
  300.   field_pack_length是之前根據new_frm_ver,即head[2] - FRM_VER得到的,
  301.   如果new_frm_ver大於等於2,field_pack_length就是17
  302.   後面多出來的n_length等位元組數是用來都用列名拼成的字串
  303.   */
  304.   read_length=(uint) (share->fields * field_pack_length +
  305.           pos+ (uint) (n_length+int_length+com_length));
  306.   /*
  307.   這裡用二級指標式為了在read_string函式內部能操作disk_buff這個指標本身,而不僅僅是它指向的內容
  308.   這個函式執行之後,disk_buff將指向新地址,而不是原來的地址
  309.   */
  310.   if (read_string(file,(uchar**) &disk_buff,read_length))
  311.     goto err; /* purecov: inspected */

  312.   // strpos指向從disk_buff開始加上([8192+260 ~ 8192+261]指定的長度)的位置
  313.   strpos= disk_buff+pos;


  314.   // 從strpos+fields*17處 讀取欄位名
  315.   memcpy((char*) names, strpos+(share->fields*field_pack_length),
  316.    (uint) (n_length+int_length));
  317.   comment_pos= names+(n_length+int_length);
  318.   memcpy(comment_pos, disk_buff+read_length-com_length, com_length);

  319.   // 欄位名存放到share->fieldnames裡
  320.   fix_type_pointers(&interval_array, &share->fieldnames, 1, &names);
  321.   if (share->fieldnames.count != share->fields)
  322.     goto err;
  323.   fix_type_pointers(&interval_array, share->intervals, interval_count,
  324.         &names);

  325.   // 逐個讀取欄位資訊
  326.   for (i=0 ; i < share->fields; i++, strpos+=field_pack_length, field_ptr++)
  327.   {
  328.     if (new_frm_ver >= 3)
  329.     {
  330.       /* new frm file in 4.1 */
  331.       // strpos[3~4] 欄位長度
  332.       field_length= uint2korr(strpos+3);
  333.       recpos= uint3korr(strpos+5);
  334.       pack_flag= uint2korr(strpos+8);
  335.       unireg_type= (uint) strpos[10];
  336.       interval_nr= (uint) strpos[12];
  337.       uint comment_length=uint2korr(strpos+15);

  338.       // strpos[13] 欄位型別
  339.       field_type=(enum_field_types) (uint) strpos[13];

  340.       /* charset and geometry_type share the same byte in frm */
  341.       if (field_type == MYSQL_TYPE_GEOMETRY)
  342.       {
  343.       #ifdef HAVE_SPATIAL
  344.         geom_type= (Field::geometry_type) strpos[14];
  345.         charset= &my_charset_bin;
  346.       #else
  347.         error= 4; // unsupported field type
  348.         goto err;
  349.       #endif
  350.       }
  351.       else
  352.       {
  353.         /*
  354.         mysql每一列支援不同的字符集
  355.         讀取順序是strpos[11],strpos[14]
  356.         */
  357.         uint csid= strpos[14] + (((uint) strpos[11]) << 8);
  358.         if (!csid)
  359.           charset= &my_charset_bin;
  360.         else if (!(charset= get_charset(csid, MYF(0))))
  361.         {
  362.           error= 5; // Unknown or unavailable charset
  363.           errarg= (int) csid;
  364.           goto err;
  365.         }
  366.       }
  367.       if (!comment_length)
  368.       {
  369.         comment.str= (char*) "";
  370.         comment.length=0;
  371.       }
  372.       else
  373.       {
  374.         comment.str= (char*) comment_pos;
  375.         comment.length= comment_length;
  376.         comment_pos+= comment_length;
  377.       }
  378.     }
  379.   }


舉例
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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章