sqlite3存储格式
本篇介紹sqlite3數(shù)據(jù)庫文件的存儲格式。
通過閱讀源讀源代碼可以知道sqlite的設(shè)計思想。
一個sqlite數(shù)據(jù)庫文件對應(yīng)著一個數(shù)據(jù)庫。sqlite將數(shù)據(jù)庫文件劃分大小一致的存儲(以區(qū)分內(nèi)存)頁面,并通過一系列數(shù)據(jù)結(jié)構(gòu)將它們組織起來。
sqlite組織頁面的數(shù)據(jù)結(jié)構(gòu)主要有B樹和二維鏈表。每一個頁面要么是B樹的葉子或結(jié)點,要么是二維鏈表的一個節(jié)點。用作B樹的頁面都有8或12字節(jié)的頁面信息頭,特別地第一個頁面除了是表的根結(jié)點是數(shù)據(jù)庫文件的根,所以它首先包含100字節(jié)的是庫信息,然后才是作為一個是B樹的根結(jié)點。另一方面閑置的頁面沒有頁面頭,它的鏈頭是在第一個頁面的庫信息頭。二維鏈表的第一維由單向鏈表實現(xiàn),第二維由數(shù)組實現(xiàn)。用作第一維節(jié)點的頁面也是一個空閑頁面,被統(tǒng)計在庫信息頭。
數(shù)據(jù)庫層面的表和索引,在底層存儲格式中用B樹結(jié)構(gòu)組織,第一張表或索引都對應(yīng)著一棵B樹。底層存儲中所有空閑(閑置)頁面,都由二維鏈表鏈接起來。
B樹頁面,有統(tǒng)一的存儲格式。首先是頁面信息頭,然后緊跟著頁內(nèi)數(shù)據(jù)排序索引(請區(qū)分數(shù)據(jù)庫的表索引,這里是數(shù)據(jù)結(jié)構(gòu)中的外部排序索引)數(shù)組;而頁內(nèi)數(shù)據(jù)存儲在頁面的尾部。數(shù)據(jù)索引和數(shù)據(jù)單元分別從正反兩個方向向頁面中間伸展,頁面中間部分形式空閑區(qū)域。
sqlite數(shù)據(jù)庫文件使用自然字節(jié)序存儲數(shù)據(jù),這種字節(jié)序有利于它里面使用到可變長整形解釋。
各種具體的數(shù)據(jù)結(jié)構(gòu)和詳細的格式編排請自行參閱源代碼,不一一貼上劃水。自行用任意一門編程語言寫程序遍歷解讀sqlite數(shù)據(jù)庫文件其中的內(nèi)容。
在這里定義本篇使用的一些術(shù)語:
頁面,數(shù)據(jù)庫文件頁面,請區(qū)分內(nèi)存頁面或虛地址空間頁面,盡管sqlite默認頁面大小也是4KB。
指針,請區(qū)分c語言指針,這里指文件內(nèi)地址索引,或頁面內(nèi)地址偏移。
單元,cell,存儲在B樹頁面的單位數(shù)據(jù)。
整型主鍵,sqlite表默認的自增計數(shù)。
變長整型,sqlite存儲中使用到的一種基于自然字節(jié)序的變長整數(shù)存儲方式。每字節(jié)低7位為有效數(shù)值位,高位標記下一字節(jié)是否繼續(xù)為當前變長整數(shù)的一部分,高位為0表示為變長整數(shù)的最后一個字節(jié)。
葉子,B樹的葉子結(jié)點。
結(jié)點,B樹的非終端結(jié)點。
索引,當提及結(jié)構(gòu)化查詢時指索引表,而當提及低層存儲格式時等同于編號,地址和指針。
前面總括了底層存儲組織,現(xiàn)在結(jié)合上一層數(shù)據(jù)庫層來看存儲組織,庫和表的存儲。sqlite將一切表和索引視作表,并以B樹的形式保存在數(shù)據(jù)庫文件。sqlite數(shù)據(jù)庫默認有一張管理表,名為sqlite_master。這張表記錄著用戶創(chuàng)建的所有表和索引的名字,左聯(lián)合表名,表的根頁面索引號以及創(chuàng)建sql語句。也就是數(shù)據(jù)庫文件的第一頁面一棵入口B樹的根,通過這棵樹可以索引到其它B樹(數(shù)據(jù)庫層面表或索引)的根。當sqlite打開某一張表時,必須遍歷管理表找出與表名匹配的記錄,從而找出記錄中根頁面索引號,才能定位到表的入口位置。索引和表是分開存儲的,表默認使用是自增計數(shù),因為存儲在B樹中,所以每條記錄都必須有一個唯一鍵。索引就是一張索引鍵與自增計數(shù)的映射表。當使用索引查詢記錄時,就相當于索引左聯(lián)合到數(shù)據(jù)記錄的表,條件為自增計數(shù)??偨Y(jié)來說,就是sqlite數(shù)據(jù)庫文件里保存了多于一棵的B樹,第一棵B樹入口位于第一頁面,其它B樹的根必須通過第一棵B樹才能索引出來。
首先我們來看sqlite數(shù)據(jù)庫的總起信息,也就是入口,位于第一頁面的開頭100字節(jié)。
<img dbheader/>
可看到數(shù)據(jù)庫文件開頭作了文件類型標記“SQLite format 3\0”。當前數(shù)據(jù)庫以4KB尺寸來劃分頁面。數(shù)據(jù)庫文件迄今被修改過461次,并且沒有空閑頁面。當前默認編碼方式為UTF-8。
接著就是第一個頁面作為B樹根的頁面信息。
頁面信息首先定義了當前頁面的類型,包含自增計數(shù)的非葉子結(jié)點并且有左孩子記錄。跟著就是當前頁面的狀態(tài)信息,有1條記錄(單元),下一條插入的記錄(單元)的位置,結(jié)點的右孩子位于索引號為141的頁面(也就是第141個頁面)。當“number of cells on this page”和“first byte of the cell content area”越靠近,表示頁面內(nèi)空閑空間越小。
下面是對第一個頁面的入口B樹跟蹤。
第一行是當前頁面號為1, 類型是結(jié)點,右孩子在141號頁面,左孩子分別是139號頁面以23為key,只有一個左孩子。
第二行是當前頁面號為139,類型是葉子,存放23條記錄。根據(jù)上下文知,是1號頁面的左孩子,并且存放小于等于key23的記錄,一共有23條記錄。
第三行是當前頁面號為141,類型是葉子,存放23條記錄。根據(jù)上下文知,是1號頁面的右孩子,是剛剛分裂成139和141兩個葉子,各存放一半數(shù)量的記錄。
下面是第139號頁面,作為左孩子葉子頁面的跟蹤。
綠色框框是我們關(guān)心的信息,描述了記錄在當前頁面的指針,記錄長度以及唯一鍵值(自增計數(shù))。根據(jù)上下文139號頁面,這個B樹葉子存放自增計數(shù)小于等于23的記錄一共23條。
藍色框框是記錄單元里的數(shù)據(jù),是表的根頁面指針,在當前情景中,2,4,5,11和12都是某張表的根頁面索引號。
下面我們對2號頁面為根的表進行跟蹤。
分別對2,4以及12號頁面為根三張表的B樹存儲組織,藍色框框葉子記錄總數(shù)與使用sqlite查詢的結(jié)果一致。
最后我們跟蹤一下空閑頁面,在本篇中使用的數(shù)據(jù)庫不包含空閑頁面,現(xiàn)在我們truncate一個表以產(chǎn)生一些空閑頁面,就拿上面舉例的12號頁面為根的表,這張表包含了1個根結(jié)點和6個葉子,其中5個葉子是左孩子,1個葉子是右孩子。
留意綠色框框,文件修改次數(shù)被加1,剛剛有一個表被清空了。
藍色框框顯示了本次清空動作,產(chǎn)生了8個空閑頁面,而鏈接這些頁面的第一節(jié)點在第60號頁面。
紅色框框顯示了放在60號頁面節(jié)點的頁面索引號,對照上面12號頁面為根的表跟蹤,可以看到被清空的表的存儲B樹所使用到的頁面都被鏈接到空閑鏈表。
介紹完以頁面為單位存儲大框架后,我們再來看頁面內(nèi)的存儲。
頁面分為三部分,頁面信息頭,單元(cell)指針(索引)數(shù)組,以及數(shù)據(jù)(記錄)單元。索引和數(shù)據(jù)分別放于頁面的一頭一尾,中間為未使用空間,各自向中間獲取分配空間來應(yīng)對增長。當某一單元被刪除,它的空間會被回收鏈入到頁面內(nèi)空閑塊鏈表。當空閑塊再次被使用時可能規(guī)格不一致,從而會產(chǎn)生碎片(零碎字節(jié)),這些碎片將不能被重用,因而必須記錄下來,供往后頁面零碎程序評級用。我猜過于零碎的頁面可能會在合適的時機進行重整。也就是單元區(qū)域遍歷所有單元,必須依賴頁面內(nèi)索引數(shù)組。只有索引數(shù)組內(nèi)索引到的單元才是有效的。對于未曾發(fā)生過單元回收重用的頁面,單元區(qū)域可以直接遍歷,但發(fā)生過單元回收后則不可,必須依賴頁面內(nèi)索引。
雖然說管理表是以B樹存儲,但是卻不是以表名為鍵而只是單純的自增計數(shù),所以當表(包括索引,視圖等)的數(shù)量多的時候,打開其中之一也沒有得益于B樹結(jié)構(gòu)。
sqlite數(shù)據(jù)庫文件使用的B樹是一種B-樹。參考我們曾經(jīng)的教材嚴女士的《數(shù)據(jù)結(jié)構(gòu)》對B-樹的定義。
一棵m階的B-樹,或為空樹,或為滿足下列特性的m叉樹:
(1)樹中每個結(jié)點至多有m棵子樹;
注:這是由sqlite數(shù)據(jù)庫頁面大小限制,對于Key為變長時,并不是固定m階,而是一個泛數(shù)多階。
(2)若根結(jié)點不是葉子,則至少有兩棵子樹;
注:當表的首個頁面空間可以容納所有數(shù)據(jù)時為葉子,當不滿足時將分裂出兩個葉子,開始變成根結(jié)點。
(3)除根之外的所有非終端結(jié)點至少有m/2的上限棵子樹;
注:當結(jié)點容量不足時結(jié)點會分裂出兩個結(jié)點,各遷移一半key。
(4)所有的非終端結(jié)點中包售下列信息數(shù)據(jù)(n,(A1,K1),(A2,K2),...(An,Kn), An+1),其中n為K(ey)的個數(shù),A(ddr)是子樹地址。
注:n個Key意味有n個左孩子,A1至An是左孩子頁面號,An+1是右孩子頁面號。n和An+1保存在頁面頭,(Ax,Kx)|x<n則是結(jié)點頁面的數(shù)據(jù)單元。
(5)所有的葉子都出現(xiàn)在同一層次上,并且不帶信息。
注:不帶信息也就是頁面類型是葉子時,數(shù)據(jù)單元忽略左孩子域。但sqlite有沒有維護樹高度未能證明。
通過本篇,相信大家已經(jīng)對sqlite數(shù)據(jù)庫有了很具體的了解了。有了上面的知識大家就能對sqlite的存儲性能進行一定的分析,比如數(shù)據(jù)庫頁面的大小對增刪改查的影響。
轉(zhuǎn)載于:https://www.cnblogs.com/bbqzsl/p/5943586.html
總結(jié)
以上是生活随笔為你收集整理的sqlite3存储格式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring 配置解析之Properti
- 下一篇: 网站建设的简单步骤