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

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

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

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

 


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

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

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

參考文獻(xiàn)
[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及其應(yīng)用研究[J].沿海企業(yè)與科技,,2010(10):45-47.
[4] 杜國祥,石俊杰.SQLite嵌入式數(shù)據(jù)庫的應(yīng)用[J].電腦編程技巧與維護(hù),,2010(14):43-46.

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