《電子技術(shù)應用》
您所在的位置:首頁 > 測試測量 > 設(shè)計應用 > SQLite系統(tǒng)構(gòu)架及虛擬機分析
SQLite系統(tǒng)構(gòu)架及虛擬機分析
來源:微型機與應用2012年第10期
黨玉春1,翟秀云1,陳明通2
(1.攀枝花學院 機電工程學院,四川 攀枝花617000; 2.攀枝花學院 材料工程學院,四川 攀枝
摘要: 就目前廣泛使用的輕量級數(shù)據(jù)庫SQLite的構(gòu)架進行分析,特別是對其中的虛擬數(shù)據(jù)庫引擎(VDBE)做了原理性的剖析,并結(jié)合實例,展示了SQLite的應用及SQLite內(nèi)部VDBE指令程序的運行方式。
Abstract:
Key words :

摘  要: 就目前廣泛使用的輕量級數(shù)據(jù)庫SQLite構(gòu)架進行分析,特別是對其中的虛擬數(shù)據(jù)庫引擎(VDBE)做了原理性的剖析,并結(jié)合實例,展示了SQLite的應用及SQLite內(nèi)部VDBE指令程序的運行方式。
關(guān)鍵詞: SQLite;構(gòu)架;VDBE;虛擬機

    SQLite是遵守ACID的輕量級關(guān)系型數(shù)據(jù)庫管理系統(tǒng),完全免費、開源,無需任何配置也無需任何安裝程序[1]。它廣泛應用在各種嵌入式系統(tǒng)中,在iOS和Android等系統(tǒng)中都是集成在各自的庫中。
    虛擬機是當前比較流行的一種軟件構(gòu)架,特別是在解釋性編程語言領(lǐng)域。在安全領(lǐng)域,虛擬機也被用于實現(xiàn)軟件的加密,是公認的一種非常高效且實用的技術(shù)手段。SQLite用較小規(guī)模的代碼用C語言實現(xiàn)了一個程序虛擬機,提高了代碼的獨立性,降低了耦合性,同時保持了很高的效率。
1 SQLite數(shù)據(jù)庫構(gòu)架
    圖1所示為SQLite系統(tǒng)的總體構(gòu)架圖[2]。整體上SQLite可以分為前端和后端:前端負責從用戶數(shù)據(jù)到平臺不相關(guān)的指令的轉(zhuǎn)換;后端處理數(shù)據(jù)流,深入到具體數(shù)據(jù)庫數(shù)據(jù)在磁盤上的操作,這些數(shù)據(jù)是和平臺相關(guān)的。SQLite的平臺無關(guān)性通過其內(nèi)部實現(xiàn)的虛擬數(shù)據(jù)庫引擎VDME(Virtual Database Engine)來完成,總地來說,就是將SQL語句先翻譯成一種專門設(shè)計的語言,然后下層再調(diào)用平臺相關(guān)的系統(tǒng)API接口,完成相應的功能。

    SQLite的源代碼由96個C語言文件(.c和.h)組成,在編譯之前會由Makefile生成一個完整的文件,即為可以在官方網(wǎng)站上下載的sqlite3.c和sqlite3.h等文件,然后編譯形成所需要的庫或者可執(zhí)行文件。
    圖1給出了SQLite的主要模塊及相互之間的關(guān)系,以下將分別介紹各個部分的功能。
    (1)接口(Interface)
    SQLite庫提供的對外不調(diào)用的接口大多數(shù)都在main.c、legacy.c和vdbeapi.c中,其他一些散布在源代碼的不同部分。對接口的查詢可以在文檔中找到詳細的介紹。為了避免命名上的沖突,所有外部可以調(diào)用的接口都以sqlite3_開頭[3]。
    (2)SQL編譯器(SQL Compiler)
    這是一個比較完整的編譯器構(gòu)架,分別完成詞法分析、語法分析和中間代碼生成。詞法分析器(Tokenizer)由C語言實現(xiàn),包含在tokenize.c中;語法分析器(Parser)由Lmon LALR(1)生成,和YACC/BISON類似,不兼容,但是生成的代碼是可重入且線程安全的,代碼包含在parse.c中;代碼生成器(Code Generator)生成虛擬機執(zhí)行的中間代碼,包含的文件相對較多,例如select.c、update.c等,大多和SQL命令同名對應。
    (3)虛擬機VM(Virtual Machine)
    代碼生成器生成的中間代碼會通過VM執(zhí)行。這部分后面會有更詳細的分解。
    (4)B-Tree(B-樹)
    數(shù)據(jù)庫在磁盤上的操作都是通過B-樹的,對應于數(shù)據(jù)庫中的每一個表或者索引都會有相應的B-樹。實現(xiàn)和接口分別在btree.c和btree.h中[4]。
    (5)頁緩存(Page Cache)
    數(shù)據(jù)的讀寫都以Chunk為單位進行,這樣可以提高效率。頁緩存負責這部分工作,同時提供了回滾(rollback)等功能,并對數(shù)據(jù)庫文件進行管理。實現(xiàn)和接口分別在pager.c和pager.h中。
    (6)系統(tǒng)接口(OS Interface)
    SQLite提供了一個系統(tǒng)抽象層,定義在os.h中。每個支持的平臺有自己對應的實現(xiàn)文件,例如os_uinx.c和os_win.c(及相應的頭文件os_unix.h和os_win.h)。
    (7)功能和測試(Utility和Test Code)
2 VDBE框架及關(guān)鍵源碼分析
    虛擬數(shù)據(jù)庫引擎VDBE(Virtual Database Engine)居于SQLite數(shù)據(jù)庫的核心部分。從整個SQLite的構(gòu)架可以看出,它處在整個系統(tǒng)的中間部分:前端代碼完成對SQL語言的編譯,相當于簡化版本的一個編譯器;后端完成物理上的操作,即利用B-Tree和Pager對物理硬盤上的數(shù)據(jù)進行實際的操作。VDBE完成了這個層次上的抽象鏈接。
    整個虛擬數(shù)據(jù)庫引擎(VDBE)由若干個C語言文件組成,主題實現(xiàn)都包含在了vdbe.c(vdbe.h)中。vdbeInt.h定義了VDBE內(nèi)部使用的各種結(jié)構(gòu)和函數(shù)原型。vdbeaux.c實現(xiàn)了VDBE內(nèi)部和整個SQLite構(gòu)建VDBE程序需要的其他功能性函數(shù)代碼。vebeaip.c包含了供外部接口函數(shù)(SQLite庫外的應用程序,如sqlite3_bind系列函數(shù))使用的一些結(jié)構(gòu)。vdbemen.c 實現(xiàn)了在vdbe的存儲管理。
    對于用戶的SQL語句,編譯器會生成一個虛擬機實例。虛擬機實例在內(nèi)部和外部是不同的。對內(nèi)看到的是一個vdbe結(jié)構(gòu)的實例,這個結(jié)構(gòu)定義在vdbeInt.h中,代碼如下:
    struct Vdbe {
    sqlite3 *db;        /* 數(shù)據(jù)庫連接 */
    Op *aOp;            /* 保存虛擬機的空間 */
    …                /* 其他指令 */
    int nOp;            /* 生成的指令的條數(shù) */
    char *zSql;        /* SQL語句 */
    …                /* 其他指令 */
    SubProgram *pProgram;  /* 虛擬機使用的其他子程序,
鏈表 */
    };
    一個虛擬機實例可以有多個子程序,每個子程序可以由多條指令組成。下面是子程序的結(jié)構(gòu):
    struct SubProgram {
    VdbeOp *aOp;            /* 指令 */
    int nOp;                /* 指令條數(shù) */
    int nMem;            /* 需要的內(nèi)部空間 */
    int nCsr;                /* 需要的游標 */
    void *token;            /* 循環(huán)觸發(fā)時需要的id */
    SubProgram *pNext;    /* 鏈表的下一個 */

 


    };
    現(xiàn)在的SQLite有142條操作指令,都定義在opcodes.h中,在vdbe.c中有相應的源代碼,將解析一些指令作為代表,詳細的技術(shù)文檔可以查看官方文檔。所有的指令大概可以分為3類:
    (1)數(shù)據(jù)操作:包含算術(shù)、邏輯運算、字符串操作等;
    (2)數(shù)據(jù)管理:主要關(guān)于內(nèi)存和磁盤的操作。內(nèi)存上如棧(stack)操作、數(shù)據(jù)的傳送等,磁盤操作主要是B-Tree和Pager模塊,包括打開及操作游標、事務(wù)的開始與結(jié)束等;
    (3)控制流:指令的跳轉(zhuǎn)。
    SQL語句在生成VDBE程序后,每條指令包含了一個操作碼(opcode)和至多5個操作數(shù)(operands:P1、P2、P3、P4和P5)。其中:
    (1)P1、P2、P3都是32 bit的帶符號整數(shù),它們通常引用的是寄存器。
    (2)P2在所有的有跳轉(zhuǎn)功能的指令中表示目的地址。例如上面的第2條指令將會跳轉(zhuǎn)到第10條指令,然后順序執(zhí)行。
    (3)P4可以是32 bit或者64 bit的帶符號整型數(shù)據(jù)、字符串、BLOB數(shù)據(jù)(二進制大對象)、函數(shù)指針等其他多樣的對象。
    (4)P5通常是無符號的字符,充當?shù)氖菢俗R位。
    在SQLite的VDBE內(nèi)部,所有的指令都是VdbeOp結(jié)構(gòu)的一個實例(定義在vdbe.h中),結(jié)構(gòu)的定義也主要是這5個操作數(shù)。
    struct VdbeOp {
    u8 opcode;    /* 操作碼類型 */
    …            /* 其他數(shù)據(jù)接口 */
    signed char p4type;    /* p4 的類型 */
    u8 p5;        /* p5是無符號字符型 */
    int p1;        /* 操作數(shù)1 */
    int p2;        /* 操作數(shù)2,通常是跳轉(zhuǎn)指令的目的 */
    int p3;        /* 操作數(shù)3 */
    union { /* ... */ } p4;        /* p4 是一個聯(lián)合,
可以有不同的類型 */
    …            /* 其他數(shù)據(jù)接口 */
};
    由代碼生成器生成的程序交由VM執(zhí)行。sqlite3_step()會觸發(fā)內(nèi)部vdbe解釋生成的vdbe指令。指令的執(zhí)行在如下的函數(shù)中進行(SQLITE_PRIVATE 即為static關(guān)鍵字),此處去掉了煩瑣的細節(jié),只展示其中的關(guān)鍵結(jié)構(gòu)和一個指令的執(zhí)行。
    SQLITE_PRIVATE int sqlite3VdbeExec(
        Vdbe *p                /* VDBE 實例 */
    ) {
        int    pc;                /* 程序計數(shù)器 */
        Op    *aOp = p->aOp;    /* 得到所有的指令 */
        Op    *pOp;            /* 當前指令 */
        int    rc=    SQLITE_OK;    /* 返回值 */
        sqlite3* db = p->db;    /* 數(shù)據(jù)庫連接實例 */
        u8 encoding = ENC(db);/* UTF-8編碼 */
        …                /* 其他初始化代碼 */
        switch ( pOp->opcode )    {    /* 在此之后就是一個
非常大的case代碼
            case OP_Goto: {
                CHECK_FOR_INTERRUPT;
                pc=pOp->p2-1;/* 調(diào)整程序計數(shù)器 */
                break;
            }
            …    /* 其他的case指令 */
        }
        …        /* 其他指令 */
    }
    這個函數(shù)是整個VDBE的核心執(zhí)行函數(shù),雖然重要,但是代碼的原理非常簡單,就是一系列的switch-case語句。在相應的case情況下,會執(zhí)行相應的底層代碼,進行數(shù)據(jù)庫的磁盤操作。
3 實驗
3.1 數(shù)據(jù)庫編程接口

    SQLite的編程模型比較簡單,下面的例子給出了一個基本的框架。
    #include "sqlite3.h"
    #include <stdlib.h>
    int main(int argc, char **argv)
    {
        char        *file = "./test.db";/* 數(shù)據(jù)庫文件 */
        sqlite3    *db = NULL;    /* 數(shù)據(jù)庫連接實例 */
        int        rc = 0;        /* 返回值 */
        sqlite3_initialize();        /* 初始庫 */
        rc= sqlite3_open_v2(file, &db,
SQLITE_OPEN_READWRITE, NULL);
        /* 準備SQL語句,生成VDBE程序 */
        sqlite3_stmt    *stmt = NULL:
        rc=sqlite3_prepare_v2(db, "SELECT * FROM FILM",
 -1, &stmt, NULL);
            if (rc != SQLITE_OK) exit(-1);
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *data = (const char*)
sqlite3_column_text(stmt, 0);
            printf("%s\n", data?data:"[NULL]");
        }
        sqlite3_finalize(stmt);
        sqlite3_close(db);            /* 關(guān)閉 */
        sqlite3_shutdown();            /* 釋放資源 */
    }
    在上面的例子中,使用了sqlite3_prepare_v2()和sqlite3_
step()函數(shù),這是和內(nèi)部的虛擬機聯(lián)系非常緊密的兩個函數(shù),也是了解SQLite虛擬機的兩個點。sqlite3_prepare_v2()完成的是將SQL語句提交給SQL編譯器,編譯成VDBE指令程序,sqlite3_step()將驅(qū)動VDBE執(zhí)行指令程序。
    從應用上來說,這僅僅是最簡單的數(shù)據(jù)庫應用框架,更多的接口信息可以查看官方的文檔。
3.2 VDBE程序分析
    在官方提供的下載中,有編譯好的命令行可執(zhí)行程序,可以作為完全的SQLite數(shù)據(jù)庫管理工具。同時,它也考慮了一些Debug和Test功能,可以利用它們深入了解SQLite的內(nèi)部機制??梢岳肧QLite命令行程序中的explain命令查看由代碼生成器生成的中間代碼的形式,這只需要在相應的SQL代碼前面加上explain就可以了。如以搜索的命令行顯示(如圖2所示,箭頭表示實際執(zhí)行順序):
    圖2中,“addr”列是虛擬機的地址編號,并不是指令執(zhí)行的順序,由于跳轉(zhuǎn)指令的存在,用箭頭標示出了指令運行的實際順序,也可以在SQLite編譯時指定相應的選項,然后利用指令“pragma vdbe_trace=on;”詳細地看到指令的運行過程和堆棧的變化情況。

    指令0~指令12都是對SQLite數(shù)據(jù)庫內(nèi)部的準備:由指令1跳轉(zhuǎn)到指令10,指令10(Transaction)開始一個事務(wù),指令11(VerifyCookie)在執(zhí)行一個指令前檢查數(shù)據(jù)庫模式是否發(fā)生了變化,當發(fā)生了變化時要重置,指令12(TableLock)將要讀的數(shù)據(jù)庫表鎖起來,指令13(Goto)跳轉(zhuǎn)到指令2。
    從指令2開始是實際的對數(shù)據(jù)庫的操作了。指令2(OpenRead)會打開一個數(shù)據(jù)庫表的只讀游標,P1作為這個游標的標志,P2是打開的數(shù)據(jù)庫表的根頁(root page),P3==0表明是主數(shù)據(jù)庫,P4表明數(shù)據(jù)庫有兩列,P5說明是以P2的值作為根頁。(OpenRead指令的各個操作數(shù)還可以有其他含義,這里只是針對這條SQL語句的解釋,請查看技術(shù)文檔。)指令3(Rewind)~指令7(Next)完成了對所有查詢數(shù)據(jù)的遍歷。指令8(Close)關(guān)閉游標,指令9(Halt)結(jié)束這個VDBE程序。
    VDBE對上層提供的就是這樣的接口,而對下層將是調(diào)用相應的接口實現(xiàn)相應的功能,并由此完成模塊上的解耦合。
    由VDBE的定義、代碼分析及以上的實驗,可以總結(jié)出SQLite的整體構(gòu)架:
    外部調(diào)用SQLite接口函數(shù)sqlite3_prepare(), SQL語句通過SQL編譯器生成對應的VDBE指令程序;
    內(nèi)部調(diào)用sqlite3_step()驅(qū)動,內(nèi)部執(zhí)行sqlite3VdbeEx-
ec(),switch-case語句執(zhí)行相應指令。底層通過B-Tree和Pager實現(xiàn)對磁盤數(shù)據(jù)庫文件的管理,如圖3所示。
    在實際應用中,可以設(shè)計一個面向應用的指令集,利用程序虛擬機設(shè)計中間抽象層,提高平臺通用性。同時程序虛擬機也為語言虛擬機、系統(tǒng)虛擬機及安全沙盒等技術(shù)提供了技術(shù)基礎(chǔ)。

參考文獻
[1] OWENS M.The definitive guide to SQLite[M].Apress,2006.
[2] KREIBICH J A.Using SQLite[M].O'Reilly Media,2010.
[3] 李蔚,陳亞峰.嵌入式數(shù)據(jù)庫SQLite及其應用研究[J].沿海企業(yè)與科技,2010(10):45-47.
[4] 杜國祥,石俊杰.SQLite嵌入式數(shù)據(jù)庫的應用[J].電腦編程技巧與維護,2010(14):43-46.

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權(quán)禁止轉(zhuǎn)載。