在上一篇里,bingxi和alex聊了關于mysql內核調試方法。前10篇是一些基礎性的內容,從本篇開始,將開始描述inndob的存儲結構,為了便于描述的方便,會將一些細節暫時隱去,在后續說到B時會串起來。
我們可以了解到oracle、sqlserver采用的是段、簇、頁的方式進行管理。很多其他的數據庫也是采用的這樣的方法。本篇,bingxi和alex討論的是頁的編號。
對應的文件為:
D:/mysql-5.1.7-beta/storage/innobase/fil/fil0fil.c
D:/mysql-5.1.7-beta/storage/innobase/include/fil0fil.h
Bingxi:“alex,我們的初級系列終于開始進入存儲部分了。存儲這邊內容,包含的還是比較多。Innodb共享存儲空間而言(獨立表空間也是一樣,這里我們只分析共享表空間),以固定大小劃分了很多個頁。假設共享存儲空間只有一個文件,那么編號就是從0開始,默認頁大小為16k。也就是文件的大小,按照16k進行劃分。假設是10M,那么就是劃分為640頁,編號從0-639。

現在問題來了,如果是多個文件呢,如何編號。Alex,你來看看。提示下,mysql的共享表空間,只允許最后一個文件為可擴展的。
”
Alex:“ok,我們通過代碼來看這個問題。我們先配置下my.ini,內容如下:
[mysqld]
innodb_data_file_path = ibdata1:10M;ibdata2:10M:autoextend
我們看下fil_io代碼的實現,
~~~
/************************************************************************
Reads or writes data. This operation is asynchronous (aio). */
ulint
fil_io(
/*===*/
??????????????????????????? /* out: DB_SUCCESS, or DB_TABLESPACE_DELETED
??????????????????????????? if we are trying to do i/o on a tablespace
??????????????????????????? which does not exist */
?????? ulinttype,????????????? /* in: OS_FILE_READ or OS_FILE_WRITE,
??????????????????????????? ORed to OS_FILE_LOG, if a log i/o
??????????????????????????? and ORed to OS_AIO_SIMULATED_WAKE_LATER
??????????????????????????? if simulated aio and we want to post a
??????????????????????????? batch of i/os; NOTE that a simulated batch
??????????????????????????? may introduce hidden chances of deadlocks,
??????????????????????????? because i/os are not actually handled until
??????????????????????????? all have been posted: use with great
??????????????????????????? caution! */
?????? ibool?????? sync,????????????? /* in: TRUE if synchronous aio is desired */
?????? ulintspace_id,/* in: space id */
?????? ulintblock_offset,?? /* in: offset in number of blocks */
?????? ulintbyte_offset,??? /* in: remainder of offset in bytes; in
??????????????????????????? aio this must be divisible by the OS block
??????????????????????????? size */
?????? ulintlen,???????? /* in: how many bytes to read or write; this
??????????????????????????? must not cross a file boundary; in aio this
??????????????????????????? must be a block size multiple */
?????? void*????? buf,??????? /* in/out: buffer where to store read data
??????????????????????????? or from where to write; in aio this must be
??????????????????????????? appropriately aligned */
?????? void*????? message)/* in: message for aio handler if non-sync
??????????????????????????? aio used, else ignored */
{
??????
?????? //1.找到對應的表空間結構
?????? HASH_SEARCH(hash, system->spaces, space_id, space,
???????????????????????????????????????????????? space->id == space_id);
?……
? //2.取得第一個文件結點
?????? node = UT_LIST_GET_FIRST(space->chain);
?????? for (;;) {
????????????? ……
??????? //文件的大小根據my.ini的配置而定
????????????? //第一個文件ibdata1是10M,因此對應的node->size為640
????????????? //第二個文件ibdata2是10M,因此對應的node->size為640
????????????? //3.假設我們查找的文件號為0-639,則對應為第一個文件。
????????????? if (node->size > block_offset) {
???????????????????? /* Found! */
???????????????????? break;
????????????? } else {
//4.假設我們查找的文件號>640,則查看是否在第二個文件中。
//假設是640,則在第二個文件的偏移量為0*16k字節處開始的一頁,也就是文件開始處,也可以勉強稱為第二個文件的第0頁,實際上是640頁。
//假設是641,則在第二個文件的偏移量為(641-640)*16k字節處開始的一頁,也可以勉強稱為第二個文件的第1頁,實際上是641頁。
???????????????????? block_offset -= node->size;
???????????????????? node = UT_LIST_GET_NEXT(chain, node);
????????????? }
?????? }???????????
? ……
? //5.計算偏移量,見前面代碼中的block_offset
?????? offset_high = (block_offset >> (32 - UNIV_PAGE_SIZE_SHIFT));
?????? offset_low? = ((block_offset << UNIV_PAGE_SIZE_SHIFT) & 0xFFFFFFFFUL)
???????????????????? + byte_offset;
? ……
//6.進行aio操作,offset_low指相對于文件頭的字節偏移,len指長度,即獲得長度,通常為16k
????? ret = os_aio(type, mode | wake_later, node->name, node->handle, buf,
??????????????????????????? offset_low, offset_high, len, node, message);
? ……
?????? return(DB_SUCCESS);
}
~~~
因此,兩個文件時的頁編號在本例中如圖2:

同樣,假設有3個文件。對應的大小分別為xMB,yMB,zMB。則第一個文件的編號為0---x*1024/16-1,第二個文件的編號為x*1024/16---(x+y)*1024/16-1,第三個文件的頁編號為(x+y)*1024/16---(x+y+z)*1024/16-1。最后一個文件的大小是可變的,可參考fil相關代碼。
Bingxi,頁編號就是這么回事情了。每個頁會一個編號,因此在每一頁的開始處,會有38個字節用于描述本頁。定義的是相對于頁頭的偏移量。
~~~
/* The byte offsets on a file page for various variables */
#define FIL_PAGE_SPACE_OR_CHKSUM 0??? /* in < MySQL-4.0.14 space id the
?????????????????????????????????? page belongs to (== 0) but in later
?????????????????????????????????? versions the 'new' checksum of the
?????????????????????????????????? page */
//這里記錄的是頁號
#define FIL_PAGE_OFFSET????????????? 4???? /* page offset inside space */
//有時候頁是連在一起的,比如所引頁,這里通過prev和next指向前一頁,后一頁。
//需要注意的是,假設本頁是第n頁,下一頁不需要是n+1,上一頁也不需要是n-1
#define FIL_PAGE_PREV?????????? 8???? /* if there is a 'natural' predecessor
?????? ??????????????????????????? of the page, its offset */
#define FIL_PAGE_NEXT?????????? 12??? /* if there is a 'natural' successor
?????????????????????????????????? of the page, its offset */
//頁中最新日志的日志序列號
#define FIL_PAGE_LSN???????????? 16??? /* lsn of the end of the newest
?????????????????????????????????? modification log record to the page */
//頁的類型
#define??? FIL_PAGE_TYPE???????? 24??? /* file page type: FIL_PAGE_INDEX,...,
?????????????????????????????????? 2 bytes */
#define FIL_PAGE_FILE_FLUSH_LSN???? 26??? /* this is only defined for the
?????????????????????????????????? first page in a data file: the file
?????????????????????????????????? has been flushed to disk at least up
?????????????????????????????????? to this lsn */
#define FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID? 34 /* starting from 4.1.x this
?????????????????????????????????? contains the space id of the page */
//這里的38表示的是長度
#define FIL_PAGE_DATA?????????? 38??? /* start of the data on the page */
~~~
因此文件劃分為很多頁,每一頁有38個字節用于描述頁頭。而我們知道的是,共享存儲空間是有很多數據庫共同使用的,假設有如下的操作順序:
1)? 創建表1,并插入數據
2)? 創建表2,并插入數據
3)? 表1插入數據
4)? 表2插入數據
如果我們每次分配一個頁,就會存儲得很凌亂。可能第n頁屬于t1,n+1頁屬于t2,n+3頁屬于t1,n+4頁屬于t2,……
這樣會降低io讀寫性能,連續讀取性能會更好些,減少了磁頭的頻繁移動。Bingxi,你覺得mysql是怎么解決這個問題的呢?
”
Bingxi:“ok,這里面就引出了一個新的結構:簇。簇是連續的頁,數量為64頁。這個我們下篇講。”
Alex:“ok”