偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

深度解析Innodb記錄格式源碼

數(shù)據(jù)庫 MySQL 數(shù)據(jù)庫運維
筆者很長時間又不寫東西了,之前已經(jīng)看過了innodb格式,但現(xiàn)在想研究一下其它方面的東西,突然發(fā)現(xiàn)這個又忘了,索性再看看把它寫下來。

 可以通過一個最普遍的插入操作來跟蹤Innodb的記錄格式,因為在插入時,系統(tǒng)得到的是公共的mysql記錄格式record,現(xiàn)在它沒有涉及到任何的存儲引擎,那么這里不管當(dāng)前這個表對應(yīng)的存儲引擎是什么,記錄格式是一樣的,對于插入,mysql函數(shù)對應(yīng)的是ha_write_row,具體到Innodb存儲引擎,實際調(diào)用的函數(shù)是ha_innobase::write_row函數(shù),那么在這里,Innodb首先會將接收到的record記錄轉(zhuǎn)換為它自己的一個元組tuple,這其實是與record對應(yīng)的innodb的表示方式,它是一個內(nèi)存的記錄,邏輯的記錄,那么在系統(tǒng)將其真正的寫入到頁面之前,這條記錄的存在方式都是這個tuple,那么下面主要是從源碼的角度研究Innodb是如何將一個tuple轉(zhuǎn)換為它的物理的存儲記錄的,主要研究代碼的實現(xiàn)邏輯及記錄的格式。

這里只介紹格式為Compact類型的記錄格式。

實現(xiàn)在某一個頁面插入一個元組(一條記錄)操作的函數(shù)是page_cur_tuple_insert,它的參數(shù)就是一個dtuple_t*類型的tuple,在這里,它首先要分配一片空間來存儲將要轉(zhuǎn)換過來的物理記錄,所以這里需要先計算空間的大小,計算方法如下:

1. 首先每條記錄都要包括下面2個部分:REC_N_NEW_EXTRA_BYTES + UT_BITS_IN_BYTES(n_null),前面表示的是這種格式的固定長度的extra部分,這部分用來存儲什么內(nèi)容后面會給出,后面表示的是所有字段中哪些字段的值是null,當(dāng)然這里只存儲那些nullable屬于的字段,如果創(chuàng)建表的時候指定是not null的話,這里就不會被存儲,那么這里是用一個位來表示一個字段的null屬性。那么上面這部分被系統(tǒng)代碼命名為extra_size變量值。

2. 統(tǒng)計每一個列中數(shù)據(jù)的長度,在統(tǒng)計這個信息的時候,又有多種情況,主要分定長字段和變長字段,對于定長字段,它的長度直接就是數(shù)據(jù)類型的長度,比如int類型的那就是4個字節(jié),rowid列就是6個字節(jié)等,沒有其它附加長度。對于變長字段而言,除了數(shù)據(jù)內(nèi)容本身的長度外,還需要計算其數(shù)據(jù)長度的存儲空間,如果字段的字義長度大于255個字節(jié),或者字段的數(shù)據(jù)類型為BLOB的,那么需要用2個字節(jié)來存儲這個字段的長度;如果定義長度小于128個字節(jié),或者小于256個字節(jié),但類型不是BLOB類型的,那么這個字段的數(shù)據(jù)長度用一個字節(jié)來存儲,除上面2種情況之外,都用2個字節(jié)來存儲。那么在這一部分中,用來存儲變長字段數(shù)據(jù)的長度的空間的長度也是被Innodb計算為extra_size的。

所以現(xiàn)在可以知道,一個innodb的記錄包括2個部分,一部分是extra_size,另一部分是數(shù)據(jù)內(nèi)容,那么這2部分的總長度就是上面計算出來的結(jié)果,這里把它定義為record_size。

接下來,申請空間,進(jìn)行元組到記錄的轉(zhuǎn)換工作。

轉(zhuǎn)換函數(shù)為rec_convert_dtuple_to_rec_new,參數(shù)有申請好的記錄空間buf,元組和索引的內(nèi)存結(jié)構(gòu)。

首先這里有一個操作是rec = buf + extra_size,變量rec表示的是數(shù)據(jù)內(nèi)容的存儲開始位置。extra_size就是上面計算出來的2個數(shù)據(jù)部分。

那么真正執(zhí)行轉(zhuǎn)換的是接下來調(diào)用的rec_convert_dtuple_to_rec_comp函數(shù),下面是其原型:

  1. void 
  2.   
  3.  rec_convert_dtuple_to_rec_comp( 
  4.   
  5.  /*===========================*/ 
  6.   
  7.           rec_t*                          rec,   /*!< in: origin of record */ 
  8.   
  9.           ulint                    extra,        /*!< in: number of bytes to 
  10.   
  11.                                                 reserve between the record 
  12.   
  13.                                                 header and the data payload 
  14.   
  15.                                                 (normally REC_N_NEW_EXTRA_BYTES) */ 
  16.   
  17.           const dict_index_t*  index,        /*!< in: record descriptor */ 
  18.   
  19.           ulint                    status,       /*!< in: status bits of the record */ 
  20.   
  21.           const dfield_t*          fields,        /*!< in: array of data fields */ 
  22.   
  23.           ulint                    n_fields)/*!< in: number of data fields */ 

 

rec表示的是剛才上面計算出來的rec變量,extra表示的是固定長度的REC_N_NEW_EXTRA_BYTES。

 

  1. end = rec; 
  2.   
  3.           nulls = rec - (extra + 1); 
  4.   
  5.           n_null = index->n_nullable; 
  6.   
  7.           lens = nulls - UT_BITS_IN_BYTES(n_null); 
  8.   
  9.           /* clear the SQL-null flags */ 
  10.   
  11.           memset(lens + 1, 0, nulls - lens); 

在這里,這段代碼一下子很難看明白,那么首先這里畫一下記錄存儲格式:

 

 

         |---------------------extra_size-----------------------------------------|---------fields_data------------|

         |--columns_lens---|---null lens----|------fixed_extrasize(5)------|--col1---|---col2---|---col2----|

那么語句nulls = rec - (extra + 1);得到的結(jié)果是什么呢?想干什么?因為extra表示的是REC_N_NEW_EXTRA_BYTES,固定長度的fixed_extrasize,rec表示的是圖中col1的開始位置,那么現(xiàn)在可以知道這條語句的結(jié)果就是使得nulls指向了前面nulllens的后一個字節(jié)的開始位置。那現(xiàn)在我們知道nulls是一個或者多個字節(jié),用來存儲每一個nullable字段的空標(biāo)志的,那現(xiàn)在為什么要指向這個數(shù)組的后一個字節(jié)的開始位置呢?一下子很難想明白,不過從后面的代碼中可以知道,寫入nulls是從后面向前面寫的,所以這也理解了為什么指向了后面一個字節(jié)的位置了。

那接下來的一個語句lens = nulls - UT_BITS_IN_BYTES(n_null);道理也是一樣的,因為columns_lens正好是在nulllens的前面,那么如果向前跳過null標(biāo)志的所有空間,則指向的位置lens就是columns_lens的后面一個字節(jié)的位置了。在寫入值的時候也是從后面向前面寫。

那最后一個語句memset(lens + 1, 0, nulls - lens);表示的意思就很明白了,因為lens指向的是columns_lens的最后一個字節(jié)的開始位置,那么加1就指向了nulls空間的開始位置,nulls – lens表示的是nulls空間的長度。這里是將nulls空間清零。

上面有兩個部分都是從后面向前面填寫數(shù)據(jù),那是不是擔(dān)心在寫入的時候會不會向前面越界呢?其實是不會的,因為這些都是在前面計算好的,extrasize已經(jīng)是固定的,包括了nulls和columns_lens的長度的。

上面算是初始化工作,下面就是根據(jù)每一個字段來填寫record記錄了,下面一段代碼是處理null信息的,對于每一個字段,都會做下面的處理:

 

  1. if (!(dtype_get_prtype(type) & DATA_NOT_NULL)) { 
  2.                 /* nullable field */ 
  3.                 ut_ad(n_null--); 
  4.                 if (UNIV_UNLIKELY(!(byte) null_mask)) { 
  5.                           nulls--; 
  6.                           null_mask = 1; 
  7.                 } 
  8.                 ut_ad(*nulls < null_mask); 
  9.                 if (dfield_is_null(field)) { 
  10.                        *nulls |= null_mask; 
  11.                        null_mask <<= 1; 
  12.                        continue
  13.               } 
  14.               null_mask <<= 1; 
  15.      } 

從第一行可以看出,要處理這個的條件首先必須是沒有定義not null屬性,所以nulls空間只存儲這些字段的信息。

 

第4行表示的是如果(byte) null_mask)為0時,nulls向前退一個字節(jié),并且將null_mask恢復(fù)為1的初值,因為這個值初始值就是1的,可以猜到,如果這個條件滿足了,則說明已經(jīng)寫入了8個nullable列了,那么需要移向前一個字節(jié)繼續(xù)寫null信息了,但發(fā)現(xiàn)null_mask是int類型的,而nulls是一個字節(jié)一個字節(jié)的填的,不匹配啊,不過仔細(xì)看,判斷條件是(byte) null_mask),所以只要寫入8個之后,這個值就為0了。因為對于每一個字段,都是執(zhí)行null_mask向左移1個位的,所以移8次之后,低8位就都是0了。

第9行表示的是如果這個列的數(shù)據(jù)就是null值,那么需要將這個null反映到nulls數(shù)組中去,因為null_mask當(dāng)前的值(其實是1的位置)其實表示的是當(dāng)前nulls這個字節(jié)中正在處理的字段的對應(yīng)關(guān)系,也就是說,如果當(dāng)前的字段的值為null,那么像第10行所示的,將null_mask或到nulls字節(jié)上去,如果不為null,就不管,對應(yīng)的位的值為0。

所以從這里可以看出,整個nulls空間中的位圖是以從后面向前面的順序來表示所有nullable列的null信息的。

 

  1. if (fixed_len) { 
  2.        } else if (dfield_is_ext(field)) { 
  3.                 *lens-- = (byte) (len >> 8) | 0xc0; 
  4.                 *lens-- = (byte) len; 
  5.        } else { 
  6.                 if (len < 128 || (dtype_get_len(type) < 256 && dtype_get_mtype(type) != DATA_BLOB)) { 
  7.                           *lens-- = (byte) len; 
  8.                 } else { 
  9.                           *lens-- = (byte) (len >> 8) | 0x80; 
  10.                        *lens-- = (byte) len; 
  11.               } 
  12.      } 
  13.      memcpy(end, dfield_get_data(field), len); 
  14.      end += len; 

從第一行可以看出,對于定長數(shù)據(jù),只需要將其數(shù)據(jù)寫入到記錄里面即可,主要處理的是變長數(shù)據(jù),第2行表示的是如果長度大于256個字節(jié),或者數(shù)據(jù)類型為BLOB,則用兩個字節(jié)來存儲其長度,低字節(jié)存儲(len >> 8) | 0xc0,高字節(jié)存儲(byte) len(被截斷)。其它可以直接看出來。

 

到13行,是直接將數(shù)據(jù)拷到數(shù)據(jù)存儲空間,用end來表示,存儲完一個字段接著下一個字段,是按照索引定義的順序存儲的。

到這里,一條記錄的邏輯到物理的轉(zhuǎn)換就完成了,從中也知道了Innodb是如何實現(xiàn)其物理記錄的存儲的。

總結(jié):看innodb的代碼,可以說它的代碼非常優(yōu)美,非常精練的,所以有些地方很難一下子看懂,需要揣測,體會才能深入的理解。同時有很多地方是直接硬編碼的,這樣導(dǎo)致更加難理解,最好的方式是通過宏將其命名,有助于理解。

原文鏈接:http://www.cnblogs.com/bamboos/archive/2013/03/04/2943160.html

【編輯推薦】

  1. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之庫操作示例
  2. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之表操作示例
  3. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之MySQL管理心得
  4. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之MySQL查詢示例
  5. 適合初學(xué)者的MySQL學(xué)習(xí)筆記之管理員常用操作總結(jié)

責(zé)任編輯:彭凡 來源: 博客園
相關(guān)推薦

2016-12-15 09:44:31

框架Caffe源碼

2022-12-07 08:02:43

Spring流程IOC

2024-09-11 09:25:03

Tomcat組件PREP

2022-08-17 07:52:31

Spring循環(huán)依賴單例池

2023-05-08 08:11:49

@Component使用場景時序圖

2021-05-26 11:30:24

Java線程池代碼

2024-01-11 12:14:31

Async線程池任務(wù)

2025-03-14 12:30:00

Redis RDBRedis數(shù)據(jù)庫

2025-06-04 02:10:35

`@scope`CSS源碼

2021-07-03 08:51:30

源碼Netty選擇器

2022-09-27 18:56:28

ArrayList數(shù)組源代碼

2025-06-04 08:30:00

seata分布式事務(wù)開發(fā)

2023-03-27 08:12:40

源碼場景案例

2023-10-10 11:02:00

LSM Tree數(shù)據(jù)庫

2013-12-09 10:34:12

2023-03-13 08:12:25

@DependsOn源碼場景

2023-03-06 11:13:20

Spring注解加載

2009-09-14 16:12:57

LINQ刪除記錄

2019-03-06 09:55:54

Python 開發(fā)編程語言

2022-05-20 10:32:49

事件循環(huán)器事件隊列鴻蒙
點贊
收藏

51CTO技術(shù)棧公眾號