sqlite3存儲格式


本篇介紹sqlite3數據庫文件的存儲格式。
通過閱讀源讀源代碼可以知道sqlite的設計思想。
一個sqlite數據庫文件對應着一個數據庫。sqlite將數據庫文件划分大小一致的存儲(以區分內存)頁面,並通過一系列數據結構將它們組織起來。
sqlite組織頁面的數據結構主要有B樹和二維鏈表。每一個頁面要么是B樹的葉子或結點,要么是二維鏈表的一個節點。用作B樹的頁面都有8或12字節的頁面信息頭,特別地第一個頁面除了是表的根結點是數據庫文件的根,所以它首先包含100字節的是庫信息,然后才是作為一個是B樹的根結點。另一方面閑置的頁面沒有頁面頭,它的鏈頭是在第一個頁面的庫信息頭。二維鏈表的第一維由單向鏈表實現,第二維由數組實現。用作第一維節點的頁面也是一個空閑頁面,被統計在庫信息頭。

數據庫層面的表和索引,在底層存儲格式中用B樹結構組織,第一張表或索引都對應着一棵B樹。底層存儲中所有空閑(閑置)頁面,都由二維鏈表鏈接起來。
B樹頁面,有統一的存儲格式。首先是頁面信息頭,然后緊跟着頁內數據排序索引(請區分數據庫的表索引,這里是數據結構中的外部排序索引)數組;而頁內數據存儲在頁面的尾部。數據索引和數據單元分別從正反兩個方向向頁面中間伸展,頁面中間部分形式空閑區域。
sqlite數據庫文件使用自然字節序存儲數據,這種字節序有利於它里面使用到可變長整形解釋。
各種具體的數據結構和詳細的格式編排請自行參閱源代碼,不一一貼上划水。自行用任意一門編程語言寫程序遍歷解讀sqlite數據庫文件其中的內容。

在這里定義本篇使用的一些術語:
頁面,數據庫文件頁面,請區分內存頁面或虛地址空間頁面,盡管sqlite默認頁面大小也是4KB。
指針,請區分c語言指針,這里指文件內地址索引,或頁面內地址偏移。
單元,cell,存儲在B樹頁面的單位數據。
整型主鍵,sqlite表默認的自增計數。
變長整型,sqlite存儲中使用到的一種基於自然字節序的變長整數存儲方式。每字節低7位為有效數值位,高位標記下一字節是否繼續為當前變長整數的一部分,高位為0表示為變長整數的最后一個字節。
葉子,B樹的葉子結點。
結點,B樹的非終端結點。
索引,當提及結構化查詢時指索引表,而當提及低層存儲格式時等同於編號,地址和指針。

前面總括了底層存儲組織,現在結合上一層數據庫層來看存儲組織,庫和表的存儲。sqlite將一切表和索引視作表,並以B樹的形式保存在數據庫文件。sqlite數據庫默認有一張管理表,名為sqlite_master。這張表記錄着用戶創建的所有表和索引的名字,左聯合表名,表的根頁面索引號以及創建sql語句。也就是數據庫文件的第一頁面一棵入口B樹的根,通過這棵樹可以索引到其它B樹(數據庫層面表或索引)的根。當sqlite打開某一張表時,必須遍歷管理表找出與表名匹配的記錄,從而找出記錄中根頁面索引號,才能定位到表的入口位置。索引和表是分開存儲的,表默認使用是自增計數,因為存儲在B樹中,所以每條記錄都必須有一個唯一鍵。索引就是一張索引鍵與自增計數的映射表。當使用索引查詢記錄時,就相當於索引左聯合到數據記錄的表,條件為自增計數。總結來說,就是sqlite數據庫文件里保存了多於一棵的B樹,第一棵B樹入口位於第一頁面,其它B樹的根必須通過第一棵B樹才能索引出來。

首先我們來看sqlite數據庫的總起信息,也就是入口,位於第一頁面的開頭100字節。
<img dbheader/>
可看到數據庫文件開頭作了文件類型標記“SQLite format 3\0”。當前數據庫以4KB尺寸來划分頁面。數據庫文件迄今被修改過461次,並且沒有空閑頁面。當前默認編碼方式為UTF-8。

接着就是第一個頁面作為B樹根的頁面信息。

頁面信息首先定義了當前頁面的類型,包含自增計數的非葉子結點並且有左孩子記錄。跟着就是當前頁面的狀態信息,有1條記錄(單元),下一條插入的記錄(單元)的位置,結點的右孩子位於索引號為141的頁面(也就是第141個頁面)。當“number of cells on this page”和“first byte of the cell content area”越靠近,表示頁面內空閑空間越小。

下面是對第一個頁面的入口B樹跟蹤。

第一行是當前頁面號為1, 類型是結點,右孩子在141號頁面,左孩子分別是139號頁面以23為key,只有一個左孩子。
第二行是當前頁面號為139,類型是葉子,存放23條記錄。根據上下文知,是1號頁面的左孩子,並且存放小於等於key23的記錄,一共有23條記錄。
第三行是當前頁面號為141,類型是葉子,存放23條記錄。根據上下文知,是1號頁面的右孩子,是剛剛分裂成139和141兩個葉子,各存放一半數量的記錄。

下面是第139號頁面,作為左孩子葉子頁面的跟蹤。

綠色框框是我們關心的信息,描述了記錄在當前頁面的指針,記錄長度以及唯一鍵值(自增計數)。根據上下文139號頁面,這個B樹葉子存放自增計數小於等於23的記錄一共23條。
藍色框框是記錄單元里的數據,是表的根頁面指針,在當前情景中,2,4,5,11和12都是某張表的根頁面索引號。

下面我們對2號頁面為根的表進行跟蹤。

分別對2,4以及12號頁面為根三張表的B樹存儲組織,藍色框框葉子記錄總數與使用sqlite查詢的結果一致。

最后我們跟蹤一下空閑頁面,在本篇中使用的數據庫不包含空閑頁面,現在我們truncate一個表以產生一些空閑頁面,就拿上面舉例的12號頁面為根的表,這張表包含了1個根結點和6個葉子,其中5個葉子是左孩子,1個葉子是右孩子。

留意綠色框框,文件修改次數被加1,剛剛有一個表被清空了。
藍色框框顯示了本次清空動作,產生了8個空閑頁面,而鏈接這些頁面的第一節點在第60號頁面。
紅色框框顯示了放在60號頁面節點的頁面索引號,對照上面12號頁面為根的表跟蹤,可以看到被清空的表的存儲B樹所使用到的頁面都被鏈接到空閑鏈表。

介紹完以頁面為單位存儲大框架后,我們再來看頁面內的存儲。
頁面分為三部分,頁面信息頭,單元(cell)指針(索引)數組,以及數據(記錄)單元。索引和數據分別放於頁面的一頭一尾,中間為未使用空間,各自向中間獲取分配空間來應對增長。當某一單元被刪除,它的空間會被回收鏈入到頁面內空閑塊鏈表。當空閑塊再次被使用時可能規格不一致,從而會產生碎片(零碎字節),這些碎片將不能被重用,因而必須記錄下來,供往后頁面零碎程序評級用。我猜過於零碎的頁面可能會在合適的時機進行重整。也就是單元區域遍歷所有單元,必須依賴頁面內索引數組。只有索引數組內索引到的單元才是有效的。對於未曾發生過單元回收重用的頁面,單元區域可以直接遍歷,但發生過單元回收后則不可,必須依賴頁面內索引。

雖然說管理表是以B樹存儲,但是卻不是以表名為鍵而只是單純的自增計數,所以當表(包括索引,視圖等)的數量多的時候,打開其中之一也沒有得益於B樹結構。
sqlite數據庫文件使用的B樹是一種B-樹。參考我們曾經的教材嚴女士的《數據結構》對B-樹的定義。
一棵m階的B-樹,或為空樹,或為滿足下列特性的m叉樹:
(1)樹中每個結點至多有m棵子樹;
注:這是由sqlite數據庫頁面大小限制,對於Key為變長時,並不是固定m階,而是一個泛數多階。
(2)若根結點不是葉子,則至少有兩棵子樹;
注:當表的首個頁面空間可以容納所有數據時為葉子,當不滿足時將分裂出兩個葉子,開始變成根結點。
(3)除根之外的所有非終端結點至少有m/2的上限棵子樹;
注:當結點容量不足時結點會分裂出兩個結點,各遷移一半key。
(4)所有的非終端結點中包售下列信息數據(n,(A1,K1),(A2,K2),...(An,Kn), An+1),其中n為K(ey)的個數,A(ddr)是子樹地址。
注:n個Key意味有n個左孩子,A1至An是左孩子頁面號,An+1是右孩子頁面號。n和An+1保存在頁面頭,(Ax,Kx)|x<n則是結點頁面的數據單元。
(5)所有的葉子都出現在同一層次上,並且不帶信息。
注:不帶信息也就是頁面類型是葉子時,數據單元忽略左孩子域。但sqlite有沒有維護樹高度未能證明。


通過本篇,相信大家已經對sqlite數據庫有了很具體的了解了。有了上面的知識大家就能對sqlite的存儲性能進行一定的分析,比如數據庫頁面的大小對增刪改查的影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM