SQL數(shù)據(jù)庫揭秘

英文原文

SQLite

本文里蛀蜜,我們將會通過SQLite的一個早期版本來討論一下數(shù)據(jù)庫實現(xiàn)的一些架構(gòu)細節(jié)

簡介

????數(shù)據(jù)庫是軟件系統(tǒng)里很重要的一個組成部分投蝉,它主要用來高效地存儲和讀取數(shù)據(jù)硬纤。本文里,我們將會通過SQLite的一個早期版本來討論一下數(shù)據(jù)庫實現(xiàn)的一些架構(gòu)細節(jié)。

????SQLite是一個小型數(shù)據(jù)庫軟件,但是它被廣泛地用在成千上萬的軟件系統(tǒng)和硬件設(shè)備里篇恒。它是在2000年8月份被D.Richard Hipp發(fā)明的。SQLite是一個高性能的,輕量級的關(guān)系型數(shù)據(jù)庫系統(tǒng)。如果你想在代碼層面學(xué)習(xí)數(shù)據(jù)庫的內(nèi)部實現(xiàn)的話阻课,SQLite是眾多開源數(shù)據(jù)庫里最好的選擇限煞,它的文檔很多,而且代碼的可讀性非常高旺上。但是,如果去閱讀最新版本的SQLite的代碼的話,就顯得比較困難了,因為它包含了很多新的特性卒煞。另外乖订,為了便于理解數(shù)據(jù)庫的實現(xiàn)原理扛点,你需要熟悉數(shù)據(jù)結(jié)構(gòu)眠饮,了解計算原理以及操作系統(tǒng)原理寨蹋。

????在這里,我們會使用SQLite 2.5.0版本。你可以在GitHub上找到SQLite的一個簡單實現(xiàn)。另外嗦枢,這個代碼倉庫里有SQLite 2.5.0版本的代碼殖演,可以拿來參考丸相。

為什么需要數(shù)據(jù)庫座硕?

????使用純文件的方式來存儲和讀取數(shù)據(jù)是效率比較低的。數(shù)據(jù)庫會通過合適的方式來組織數(shù)據(jù),這樣的話,數(shù)據(jù)的讀取和寫入都會比較快年局。數(shù)據(jù)可以是結(jié)構(gòu)化的,半結(jié)構(gòu)化的或者完全非結(jié)構(gòu)化的。根據(jù)存儲的數(shù)據(jù)類型验庙,數(shù)據(jù)結(jié)構(gòu)可以分為以下幾類:

  1. 關(guān)系型數(shù)據(jù)庫:常用的數(shù)據(jù)庫搏恤,基于表結(jié)構(gòu)的熟空。數(shù)據(jù)表之間可以有關(guān)聯(lián)關(guān)系。這種數(shù)據(jù)庫可以用SQL語言來操作數(shù)據(jù)息罗。
  2. 鍵-值數(shù)據(jù)庫:通過鍵-值對來存儲數(shù)據(jù)掂咒。數(shù)據(jù)可以通過指定的鍵來讀取。內(nèi)存數(shù)據(jù)庫大多都是這種類型的數(shù)據(jù)庫迈喉。
  3. 對象數(shù)據(jù)庫绍刮。數(shù)據(jù)結(jié)構(gòu)更像一個對象而不是一個表。
  4. 圖數(shù)據(jù)庫:圖數(shù)據(jù)庫存儲的是節(jié)點和邊挨摸,常用于數(shù)據(jù)挖掘和社交媒體應(yīng)用录淡。

SQLite 數(shù)據(jù)庫架構(gòu)

????SQLite數(shù)據(jù)庫架構(gòu)可以分為兩個部分,分別叫做內(nèi)核(core)和后臺(backend)油坝。內(nèi)核部分包含接口,分詞器刨裆,解析器澈圈,代碼生成器和虛擬機,它負責(zé)生成數(shù)據(jù)庫事務(wù)的執(zhí)行指令帆啃。后臺包含B-tree瞬女,頁(Pager)以及用來讀取文件的操作系統(tǒng)接口。分詞器努潘,解析器和代碼生成器合起來成為編譯器诽偷,它負責(zé)生成在虛擬機上執(zhí)行的指令坤学。

從哪里開始?

????了解一個數(shù)據(jù)庫的架構(gòu)报慕,你需要有以下預(yù)備知識:

  1. 熟悉數(shù)據(jù)結(jié)構(gòu)和算法深浮。特別是B-tree,鏈表眠冈,哈希表等數(shù)據(jù)結(jié)構(gòu)飞苇。
  2. 對計算機系統(tǒng)結(jié)構(gòu)(computer architecture)有一定的了解。例如文件是如何從磁盤里進行讀寫的蜗顽,分頁系統(tǒng)以及緩存是如何工作的布卡。
  3. 一些計算原理的知識,例如有限自動機以及正則表達式雇盖。

SQLite 架構(gòu)

image

虛擬文件系統(tǒng)VFS(Virtual File System)

????在Unix和Windows兩種系統(tǒng)中忿等,文件訪問的方式是不同的。對此崔挖,VFS提供了與操作系統(tǒng)無關(guān)的通用API贸街。這套API里包含了文件的打開,讀取虚汛,寫入和關(guān)閉這些功能匾浪。以下是VFS里里用來讀取和寫入文件的API。

    Create a connection to file to read write 
    zFilename : file name 
    id : file pointer 
    pReadonly : read or write 
    */
    int sqliteOsOpenReadWrite(const char *zFilename, OsFile *id,int *pReadonly);
    Acqure the lock to read file. This function should be 
    called before caling to any file read function. 
    Return 0 if success
    id : file pointer 
    */
    int sqliteOsReadLock(OsFile *id);
    Get the write lock to write into a file. This function should called before
    doing any write operation into the file system.
    Return 0 if success
    id : file pointer
    */
    int sqliteOsWriteLock(OsFile *id);
    Move to the given number of offest to read or write into the file
    */
    int sqliteOsSeek(OsFile *id, int offset);
`/* 
Read amt bytes from the file with offset pointed by sqliteOsSeek
*/
int sqliteOsRead(OsFile *id, void *pBuf, int amt);`

`/* 
Write amt bytes from the pBuf into the file
*/
int sqliteOsWrite(OsFile *id, const void *pBuf, int amt);`

????首先卷哩,你可以通過sqliteOpenReadWrite函數(shù)來打開一個文件蛋辈。它會返回一個文件指針,你可以用這個指針來進行文件的讀寫操作将谊。接下來冷溶,如果要進行讀寫的話,就需要先獲取相應(yīng)的鎖尊浓。對于只讀操作逞频,只獲取讀鎖。對于事務(wù)而言栋齿,無論是讀寫苗胀,都需要獲取寫鎖。

????讀寫文件的尋址操作可以通過給sqliteOsSeek函數(shù)傳入指定的位移來完成瓦堵。文件位移是一個整數(shù)基协,它表示要讀寫的位置距離文件起始處的字節(jié)數(shù)。

頁(Pager)

????頁是數(shù)據(jù)庫在文件系統(tǒng)里的數(shù)據(jù)存儲的最小單元菇用。它是數(shù)據(jù)庫從磁盤讀取數(shù)據(jù)的最小單位澜驮,當(dāng)數(shù)據(jù)庫需要加裝數(shù)據(jù)的時候,它會一次性讀取整個頁的數(shù)據(jù)惋鸥。如果被加載的頁被訪問比較頻繁的話杂穷,它就會被保存在數(shù)據(jù)庫引擎的緩存里悍缠。頁都會被編號,起始號是1耐量。第一頁被稱為根頁飞蚓,而且一個頁的大小是固定的。

    Open pager with the given file name
    */
    int sqlitepager_open(Pager **ppPager,const char *zFilename,int nPage,int nEx);
    Get page specified by the page number
    */
    int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage);
    Start to write data into a page specified in pData
    */
    int sqlitepager_write(void *pData);
    Commit page changes into the file
    */
    int sqlitepager_commit(Pager*);
    Close the connection to the file
    */
    int sqlitepager_close(Pager *pPager);

Btree

????Btree是一個用來存儲數(shù)據(jù)的樹狀數(shù)據(jù)結(jié)構(gòu)拴鸵。最簡單的Btree是二叉樹(Binary tree)玷坠。數(shù)據(jù)庫使用Btree來保存索引,用以提高其性能劲藐。Btree的每個節(jié)點都會保存一列用來進行索引的值八堡。而且你可以為數(shù)據(jù)表的每一列都創(chuàng)建一個Btree索引。每個Btree都包含一個根節(jié)點聘芜,它是搜索的起始點兄渺。

“游標(biāo)(Cursor)”是用來標(biāo)識Btree里的一條記錄的一個特殊指針。游標(biāo)通過頁碼(page id)和位置(offset)來索引一個數(shù)據(jù)記錄汰现。SQLite通過master_table這個表來存儲數(shù)據(jù)表元數(shù)據(jù)挂谍,并且master_table的數(shù)據(jù)是存放在數(shù)據(jù)庫的第一個數(shù)據(jù)頁里。

    Open file connection to a page file name specified by zFileName with 
    nCache size cache
    */
    int sqliteBtreeOpen(const char *zFilename, int mode, int nCache, Btree **ppBtree)
    Start transaction. This function should called before any btree modification 
    operations
    */
    int sqliteBtreeBeginTrans(Btree *pBt)
    Insert key pKey with nKey byte and value pData with nData byte put 
    into the Btree
    */
    int sqliteBtreeInsert(BtCursor *pCur, const void *pKey, int nKey, 
      const void *pData, int nData)
    Write data into the file
    */
    int sqliteBtreeCommit(Btree *pBt)
    Move cursor to the matching pKey with nKey bytes
    */
    int sqliteBtreeMoveto(BtCursor *pCur, const void *pKey, int nKey, int *pRes)

VDBE(虛擬數(shù)據(jù)庫系統(tǒng)瞎饲,Virtual Database Engine)

????虛擬數(shù)據(jù)庫系統(tǒng)(VDBE)是一個可以運行代碼生成器生成的指令的虛擬機口叙。包括insert,delete嗅战,update妄田,select在內(nèi)的SQL指令都會被轉(zhuǎn)換成一系列指令,然后這些指令都會在虛擬機上運行驮捍。每個指令包含三個輸入?yún)?shù)疟呐,分別是p1,p2和p3东且。你可以把它們看著是函數(shù)的輸入?yún)?shù)類似启具。

????以下是我為下面這個select語句構(gòu)造的執(zhí)行指令棧。PC是程序計數(shù)器的指令id珊泳。SQLite中比較有趣的一點是鲁冯,你可以通過在SQL語句前面加上explain命令來獲取該SQL對應(yīng)虛擬數(shù)據(jù)庫系統(tǒng)(VDBE)的指令。

explain select * from foo;

PC OPCode P1 P2 P3 備注
1 ColumnCount 1 0 設(shè)置列數(shù)為1
2 ColumnName 0 0 value 設(shè)置列名為“value”
3 Open 0 3 foo 打開游標(biāo)(cursor)色查,并且指向第3頁晓褪,該頁是foo表的根頁
4 VerifyCookies 46 0 確認表結(jié)構(gòu)沒有變動
5 Rewind 0 11 重置游標(biāo)到第一個數(shù)據(jù)項
6 Column 0 0 從Btree里讀取數(shù)據(jù),并存放在棧里
7 Column 0 0
8 Ne 1 10 取出棧頂?shù)膬蓚€元素進行對比综慎。如果相等,就跳到P2的指令勤庐;否則的話示惊,繼續(xù)執(zhí)行下一條指令好港。
9 Callback 1 0 從棧頂取出P1個值并且轉(zhuǎn)換成一個數(shù)組,這就是SQL表達式的執(zhí)行結(jié)果米罚。
10 Next 0 5 將游標(biāo)移動到下一條記錄上钧汹,如果存在數(shù)據(jù)的話,就跳到P2所對應(yīng)的指令繼續(xù)執(zhí)行录择;否則拔莱,就繼續(xù)執(zhí)行下一條。
11 Close 0 0 關(guān)閉游標(biāo)

編譯器

????分詞器隘竭,解析器以及代碼生成器組合起來就是編譯器塘秦,它可以生成在虛擬數(shù)據(jù)庫引擎(VDBE)上執(zhí)行的指令。分詞器通過掃描SQL語句來生成一系列的標(biāo)識符(Token)动看。然后尊剔,再校驗語法并生成語法樹(parse tree)。最后菱皆,代碼生成器將語法樹轉(zhuǎn)換成一段SQLite指令的小程序须误。

結(jié)論

????SQLite是一個簡單,輕量級仇轻,高性能的關(guān)系型數(shù)據(jù)庫京痢,它被廣泛用于軟件系統(tǒng)設(shè)計。早期版本的SQLite的實現(xiàn)架構(gòu)簡單篷店,并且代碼可讀性非常高祭椰。頁提供了一種將文件讀取封裝成對固定大小數(shù)據(jù)塊進行操作的抽象層。Btree提供了在內(nèi)存里高效存儲和訪問數(shù)據(jù)的途徑船庇。最后吭产,當(dāng)SQL被加載到SQLite數(shù)據(jù)庫的時候,會被轉(zhuǎn)換成SQLite機器碼并在虛擬數(shù)據(jù)庫系統(tǒng)(VDBE)上執(zhí)行鸭轮。最終結(jié)果會通過API返回給用戶臣淤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窃爷,隨后出現(xiàn)的幾起案子邑蒋,更是在濱河造成了極大的恐慌,老刑警劉巖按厘,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件医吊,死亡現(xiàn)場離奇詭異,居然都是意外死亡逮京,警方通過查閱死者的電腦和手機卿堂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人草描,你說我怎么就攤上這事览绿。” “怎么了穗慕?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵饿敲,是天一觀的道長。 經(jīng)常有香客問我逛绵,道長怀各,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任术浪,我火速辦了婚禮瓢对,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘添吗。我一直安慰自己沥曹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布碟联。 她就那樣靜靜地躺著妓美,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲤孵。 梳的紋絲不亂的頭發(fā)上壶栋,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音普监,去河邊找鬼贵试。 笑死,一個胖子當(dāng)著我的面吹牛凯正,可吹牛的內(nèi)容都是我干的毙玻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼廊散,長吁一口氣:“原來是場噩夢啊……” “哼桑滩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起允睹,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤运准,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缭受,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胁澳,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年米者,在試婚紗的時候發(fā)現(xiàn)自己被綠了韭畸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胰丁,靈堂內(nèi)的尸體忽然破棺而出普筹,到底是詐尸還是另有隱情,我是刑警寧澤隘马,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站妻顶,受9級特大地震影響酸员,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讳嘱,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一幔嗦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沥潭,春花似錦邀泉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拔恰,卻和暖如春因谎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颜懊。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工财岔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人河爹。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓匠璧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咸这。 傳聞我的和親對象是個殘疾皇子夷恍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容