在上一篇里,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
*/
這個定義是從右往左的,如果轉化為從左往右,則如下圖所示:

因此,我們繼續看代碼,假設我們已經得到一個記錄指針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,打開內存監控輸入該地址。

從指針向前數出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。

根據控制信息可以知道該記錄的字段數為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”