🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在上一篇里,bingxi和alex聊了关于簇页管理。Innodb的记录分为新旧两种格式,在本篇里,bingxi和alex会讨论下innodb的旧式记录结构。 对应的文件为: D:/mysql-5.1.7-beta/storage/innobase/rem/rem0rec.c D:/mysql-5.1.7-beta/storage/innobase/include/rem0rec.h D:/mysql-5.1.7-beta/storage/innobase/include/rem0rec.ic ## 1)innodb旧式结构组成 Bingxi:“alex,mysql存储的最基本的结构是记录。B树的内结点和叶结点都是由记录组成。实际存储的内容如下: 内容1:存放字段偏移量,用于指明字段的偏移量。长度为字段数*1或者字段数*2 内容2:长度为6,存放记录的控制信息。 内容3:存放实际的内容(记录指针指向内容3的开始处) Alex,你在代码中看下控制信息相关的6个字节的定义。 ” Alex:“好的,我们看下rem0rec.ic的中旧式记录的控制结构的定义。 /* Offsets of the bit-fields in an old-style record. NOTE! In the table the most significant bytes and bits are written below less significant.        (1) byte offset        (2) bit usage within byte        downward from        origin ->  1     8 bits pointer to next record                      2     8 bits pointer to next record                      3    1 bit short flag                             7 bits number of fields                      4     3 bits number of fields                             5 bits heap number                      5     8 bits heap number                      6     4 bits n_owned                             4 bits info bits */ 这个定义是从右往左的,如果转化为从左往右,则如下图所示: ![](https://box.kancloud.cn/2016-07-22_5791c9c3d5df7.gif) 因此,我们继续看代码,假设我们已经得到一个记录指针p,那么我们如何获得对应的控制信息。 ~~~ /********************************************************** The following function is used to get the number of fields in an old-style record. */ UNIV_INLINE ulint rec_get_n_fields_old( /*=================*/                      /* out: number of data fields */        rec_t*     rec)/* in: physical record */ {        ulintret;        ut_ad(rec);     //在这里设置断点        ret = rec_get_bit_field_2(rec, REC_OLD_N_FIELDS,                             REC_OLD_N_FIELDS_MASK, REC_OLD_N_FIELDS_SHIFT);        ut_ad(ret <= REC_MAX_N_FIELDS);        ut_ad(ret > 0);        return(ret); }     设置断点,可以看到rec在此次终端时的值为0x0119808c,打开内存监控输入该地址。 ![](https://box.kancloud.cn/2016-07-22_5791c9c3ef9ce.gif) 从指针向前数出6个字节,这六个字节是 00 00 10 13 00 ce 根据前面的推算,可以得知如下信息: 4bits info:全为0,也就是表该字段有效 4bits n_owned: 值为0 13bits heap_no: 值为2 10bits n_fiels: 值为9 1bit 1bytes_offs_flag: 值为1,因此1个字节可以表示一个偏移 16bits next 16 bits: 值为0xce 带这这些信息,我们来验证代码,按F11进入rec_get_bit_field_2函数。 /********************************************************** Gets a bit field from within 2 bytes. */ UNIV_INLINE ulint rec_get_bit_field_2( /*================*/        rec_t*     rec,  /* in: pointer to record origin */        ulintoffs,       /* in: offset from the origin down */        ulintmask,      /* in: mask used to filter bits */        ulintshift)       /* in: shift right applied after masking */ {        ut_ad(rec); //在本例中 //rec为0x0119808c //offs为4 //mask为0x000007fe即,0000 0111 1111 1110 //shift为1 //步骤1:将指针-4,也就是图1中字节3的起始位置,通过与mask的与操作,将与n_fields相关的10个字节“与”出来,将结果右移一位,就得到记录数        return((mach_read_from_2(rec - offs) & mask) >> shift); } ~~~ 继续往下执行,得到返回值9。获取控制信息其他字节的方法类似。 我们接着往下看字段偏移量的类型,在这6个控制信息之前存放的是字段偏移量,也就是相对于记录指针的偏移量。 我们继续进行调试,在rec_1_get_field_start_offs函数设置断点,可以看到rec的值为0x011ac122。 ![](https://box.kancloud.cn/2016-07-22_5791c9c41fcc7.gif) 根据控制信息可以知道该记录的字段数为10个。这10个偏移量如下: 34 b0 30 2c 24 20 1c 14 0d 07 这些偏移量是反向存储的,实际上对应的各字段的长度为: 字段0:7 字段1:6 (0d-07=6) 字段2:7 (14-0d=7) 字段3:8 (1c-14=8) 字段4:4 (20-1c=4) 字段5:4 (24-20=4) 字段6:8 (2c-24=8) 字段7:4  (30-2c=4) 字段8::0  (b0的最高位为1,表示该字段为null,b0去掉最高位的0,同样是30) 字段9: 4 (34-30=4) 因此,字段1存储的7是第一个字段的偏移量么?也就是第一个字段开始值是p+7?那么p+0是什么?ok,很明显字段从0开始编码。看下面的代码。 ~~~ /********************************************************** Returns the offset of nth field start if the record is stored in the 1-byte offsets form. */ UNIV_INLINE ulint rec_1_get_field_start_offs( /*=======================*/                     /* out: offset of the start of the field */       rec_t*     rec,        /* in: record */       ulintn)    /* in: field index */ {        ut_ad(rec_get_1byte_offs_flag(rec));        ut_ad(n <= rec_get_n_fields_old(rec));     //步骤1:如果是获得第0个字段的起始地址,那么就是0        if (n == 0) {               return(0);        } //步骤2:否则调用函数rec_1_get_prev_field_end_info // rec_1_get_prev_field_end_info的实现为: // mach_read_from_1(rec - (REC_N_OLD_EXTRA_BYTES + n)) //因此在本例中,假设n为1,则返回7 //假设n为2,则返回13。        return(rec_1_get_prev_field_end_info(rec, n)                                           & ~REC_1BYTE_SQL_NULL_MASK);  } ~~~ 这段代码中出现了宏REC_1BYTE_SQL_NULL_MASK,是因为偏移量的最高为表示是否为null。 当偏移量是1字节时,最高位为0,则是非NULL,为1,则该字段是null。其他的7bit用于表示偏移量,因此可以表示的最大偏移量为127。 当偏移量为2字节时,最高位为0,则是非null,为1,则该字段是null,次最高位用于表示是否字段存储在同一页。 经过重组,本例的记录进行梳理如下: 34 b0 30 2c 24 20 1c 14 0d 07 00 00 20 15 01 bb        //6个字节的控制信息 74 65 73 74 2f 74 31     //test/t1 字段0:7字节 00 00 00 00 07 04        //字段1:6字节 00 00 00 00 35 02 50     //字段2:7字节 00 00 00 00 00 00 00 0e  //字段3:8字节 80 00 00 02              //字段4:4字节 00 00 00 01              //字段5:4字节 00 00 00 00              //字段6:8字节 00 00 00 00 00 00 00 00  //字段7:4字节                          //字段8:null 00 00 00 00              //字段9:4字节 建议将文件中的旧式记录的函数都阅读下。Bingxi,你知道旧式记录用于什么地方么?而新式的又用在什么地方? ” Bingxi:“默认情况下,5.1.7版本中,数据字典使用还是旧式记录,而用户自己创建的innodb表使用的是新式存储结构。在下一篇里,我们聊下新式记录格式。” Alex:“ok”