數據庫系統——關係型數據在磁盤上的存儲佈局

關係型數據在磁盤上的存儲佈局
1.基於page的heap file
Heap file是保存page數據的一種數據結構。從功能上來說,Heap file類似於內存數據結構中的鏈表。它可以作爲通用數據項的一種無序容器。

Heap file和鏈表結構類似的地方:

–高效的增加(append)功能

–支持大規模順序掃描

–不支持隨機訪問

下面是Heap file自有的一些特性:

–數據保存在二級存儲體(disk)中:Heapfile主要被設計用來高效存儲大數據量,數據量的大小隻受存儲體容量限制;

–Heapfile可以跨越多個磁盤空間或機器:heapfile可以用大地址結構去標識多個磁盤,甚至於多個網絡;

–數據被組織成頁;

–頁可以部分爲空(並不要求每個page必須裝滿);

頁面可以被分割在某個存儲體的不同的物理區域,也可以分佈在不同的存儲體上,甚至是不同的網絡節點中。我們可以簡單假設每一個page都有一個唯一的地址標識符PageAddress,並且操作系統可以根據PageAddress爲我們定位該Page。

下面是heap file的一些主要的接口:

class HeapFile

{

PageIterator scan();

PageAddress allocate();

void deallocate(PageAddress);

Page read(PageAddress);

void write(PageAddress, Page);

PageAddress find_free(int minsize);

};

class PageIterator

{

Page first();

boolean hasNext();

Page next();

};

1.1 基於鏈表的heap file實現
一種簡單的基於鏈表的heap file實現方案的磁盤結構佈局:

但是這種方案存在這樣的問題:HeapFile::find_free(int minsize)的開銷比較大。如上圖,我們要定位存在剩餘空間的page(page3),那麼我們必須從開始遍歷很多page(page1和page2)。我們發現,這時的遍歷I/O開銷是這個數據存儲區,顯然這樣I/O開銷就太高。

1.2 基於索引的heap file實現
一個簡單的,但是比較高效的heap file的實現是:用少量的block去存放所有page的地址索引和它們剩餘的空間大小。

從page1到page2都是滿的,它們的剩餘空間大小n1和n2都爲零。我們尋找一個存在剩餘空間的page仍然需要遍歷索引,但是這時我們的開銷僅僅是一個磁盤的I/O。

如果索引page是多個的話,那麼我們可以採用鏈表的形式存儲page索引,如下圖所示

我們假設有如下參數:

Page size:16K

Page address size:32bits

那麼,每個索引項(即是索引Page的記錄項)的大小:

Bits for free space+Bits for page addresslog2(16 KB)+32=17+32 bits=49 bits

則,每個索引page能索引的數據大小爲:

16 KB/49 bits=2674 pages=42 MB

2.數據的序列化
我們稍稍從磁盤整體佈局離題討論一下如何從tuple record轉化成字節數組。

2.1定長record的序列化
對於一些關係型數據,所有的record必須具有相同的長度。例如,思考一下用下面的SQL定義的關係型schema:

CREATE TABLE Person1 (

name      CHAR(100) NOT NULL,

age       INTEGER NOT NULL,

birthdate DATETIME NOT NULL

)

所有的記錄都具有一樣的字節數:

100 * sizeof(CHAR) + sizeof(INTEGER) + sizeof(DATETIME)

那麼定長record可以採用如下的序列化方式:

從schema上來說,我們可以檢查每個字段的大小,從而將字節數組解碼成不同的字段。

2.2變長record的序列化
假設我們改變上面Person1表的schema定義,使name可以是不超過1024個字符。Schema如下:

CREATE TABLE Person2 (

name      VARCHAR(1024) NOT NULL,

age       INTEGER NOT NULL,

birthdate DATETIME

)

上面table的record是邊長的是由於:

–name字段是一個變長的字符串;

–birthdate可以爲NULL;

變長record的序列化的關鍵是字段邊界的界定。一種比較流行的方法是在record的首部保存字段邊界的offset。

Person2的record的編排方式如下:

Note:我們在首部設置4個整型去存儲三個字段的四個邊界offset。

上面的編排方式很自然的提供一種NULL字段的編排方式–可以標識該字段的值爲NULL,如下圖:

第三個offset和第四個offset指向同一個位置,那麼就表明第三個字段的大小是零,即是一個NULL值。

3 Page Format
現在我們要具體的描述一下數據在page中是如何編排的。

我們做一個簡單化的設想:每個page中的tuple record具有相同的schema。

這種簡單化的設想有兩個重要的結果:

1. 每個關係表至少佔用一個page;

2. 每個page中保存的要麼是定長的數據,要麼是變長數據;

在Page編排格式中有兩個變量:一個是爲了定長數據,一個是爲了變長數據。對於DMS(Database Management System)來說,Page Format必須具有如下的特性:

1.高效利用磁盤空間;

2.支持scan、insert、delete和modify record的功能,並且要求高效和儘量少的I/O;

3.有一個高效的爲每一個record分配唯一ID的方式。並且某個record的ID不會由於數據庫的modify、insert、delete而改變;

4.每個record的大小不能超過一個Page的有效存儲大小,這個特性在以後的章節將會被放寬。

3.1基於定長record的page format
在Page中組織編排定長record相對來說是比較容易的。常用的方法是將一個Page的初始數據段分割成大小相等的M個slot,每個slot裝載一個record。Page的最後一段存儲着一個整數M和標識對應的slot是否爲空的M bit。

定長record的Page的維護也非常簡單。

Record ID

record r的Record ID可以是這種結構<pid§, offset®>。

Record ID的一個重要的特性是它的分配是持久的——它不因數據庫的改變而受到影響。甚至,Record ID作爲數據庫定位該record在物理磁盤上位置的依據,是完全能勝任的。

創建一個新的Record

將record r插入到page P中,

1.將page P中的最後sizeof(int)個字節讀出來作爲M,

2.然後在倒着遍歷M bits,

3.如果所有M bit都是1,那就表明page P沒有多餘的空間存儲record r,

4.如果K-th bit是0(當然這個K是離結尾最近的那個爲0的),那麼,record r可以存儲在P[nk:n(k+1)]的位置,其中n=sizeof®,最後將K-th bit置爲1.

修改record

修改一個record,我們可以很簡單的用該record的新值去覆蓋舊的值。這是由於都是定長數據,它的邊界不會因爲record的值而受到影響。

刪除record

刪除一個record,就將對應的那個bit從1改變成0即可。

3.2.變長record的Page Format
在一個Page中保存變長數據以及對該數據的維護相對來說都是比較困難的,因爲:

1.record的邊界以及字段的邊界都依賴於數據內容(即是record的值);

2.Record被修改時,record的大小可能會改變,這就造成物理上對該record的重新定位。這就需要一個非常高效的在一個Page內的或是多個Page的record遷移方法;

3.如果Record ID需要持久,那麼儘管該record已經被遷移,那麼該record被分配到的Record ID也不能改變。

一個有效的設計方法是維護一個Page首部去跟蹤free空間和record的索引。

Record ID的分配

Record ID是由page ID和目錄索引組成的。

創建一個新的record

將record r插入到page P中,

1.確保page P中的剩餘空間能存放sizeof®的數據和(sizeof(offset)+sizeof(int))的目錄索引;

2.將r從剩餘空間的開始寫入;

3.修改目錄索引信息:offset和record size;

4.修改空餘空間指針的指向。

刪除record

將record i從page P中刪除:

1.如果i是最後一個record,我們只需將最後一個目錄索引刪除,在修改空餘空間的指向即可;

2.如果i不是最後一個,我們只需將該record對應的目錄索引的offset設置爲-1即可。

修改record

假設r是一箇舊的值,r’是一個新的值。i是r的索引。

如果|r’|<=|r|,那麼就將新的值寫的offset(i),修改對應的目錄索引的size:sizeof(r’)。

如果|r|<=|r’|:

–如果該record所在的page P還有空餘的空間能夠存放r’,那麼

1.將r’寫入page P;

2.修改offset(i),更新到r’存儲的位置;

3.更新空餘空間的指針。

–如果剩餘空間不足以存放r’,那麼我們就嘗試壓縮page P(由於刪除操作使得page中有很多空洞,所以可以壓縮)。壓縮後,如果存在足夠的空間存放r’,那麼就按上面的順序增加r’;如果還是沒有足夠的空間存放r’,那麼:

1.申請一個新page,將r’寫入到新page中。由於r’在不同的page中,所以它有一個不同的Record ID;

2.將r’的Record ID寫入到r中去。

4.處理比較大的record
在這個部分,我們專注討論一下一個record保存在多個page中的方法。

4.1.record跨page保存的動因
一個明顯的動因是當一個record的大小超出了page size。record域被設計用來承載一個大的對象,比如:文件,多媒體數據等等。幾乎可以肯定的是,BLOB記錄必須跨越多個page。

另一個動因是避免空間的浪費。思考一下,在上面介紹的定長record的存儲中,如果record的大小略大於1/2page size,那麼我們的一個page只能存放一個record,我們浪費幾乎浪費了一半的存儲空間。在這種情況下,我們採用record跨page存儲將獲益匪淺。

4.2.Spanned records(跨page的record)
–如果一個record存儲在多個page中,那麼我們就說它spanned。否則,unspanned。

–Spanned record分別保存在不同的record fragment中。每個片段在一個page中。

–每個record必須用1 bit去標識該record是否spanned。

–每一個record fragment都需要保存下一個record fragment的地址,使得衆多的record fragment能夠串聯起來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章