摘 要: 就目前廣泛使用的輕量級(jí)數(shù)據(jù)庫(kù)SQLite的構(gòu)架進(jìn)行分析,特別是對(duì)其中的虛擬數(shù)據(jù)庫(kù)引擎(VDBE)做了原理性的剖析,并結(jié)合實(shí)例,展示了SQLite的應(yīng)用及SQLite內(nèi)部VDBE指令程序的運(yùn)行方式。
關(guān)鍵詞: SQLite;構(gòu)架;VDBE;虛擬機(jī)
SQLite是遵守ACID的輕量級(jí)關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),完全免費(fèi)、開源,無(wú)需任何配置也無(wú)需任何安裝程序[1]。它廣泛應(yīng)用在各種嵌入式系統(tǒng)中,在iOS和Android等系統(tǒng)中都是集成在各自的庫(kù)中。
虛擬機(jī)是當(dāng)前比較流行的一種軟件構(gòu)架,特別是在解釋性編程語(yǔ)言領(lǐng)域。在安全領(lǐng)域,虛擬機(jī)也被用于實(shí)現(xiàn)軟件的加密,是公認(rèn)的一種非常高效且實(shí)用的技術(shù)手段。SQLite用較小規(guī)模的代碼用C語(yǔ)言實(shí)現(xiàn)了一個(gè)程序虛擬機(jī),提高了代碼的獨(dú)立性,降低了耦合性,同時(shí)保持了很高的效率。
1 SQLite數(shù)據(jù)庫(kù)構(gòu)架
圖1所示為SQLite系統(tǒng)的總體構(gòu)架圖[2]。整體上SQLite可以分為前端和后端:前端負(fù)責(zé)從用戶數(shù)據(jù)到平臺(tái)不相關(guān)的指令的轉(zhuǎn)換;后端處理數(shù)據(jù)流,深入到具體數(shù)據(jù)庫(kù)數(shù)據(jù)在磁盤上的操作,這些數(shù)據(jù)是和平臺(tái)相關(guān)的。SQLite的平臺(tái)無(wú)關(guān)性通過其內(nèi)部實(shí)現(xiàn)的虛擬數(shù)據(jù)庫(kù)引擎VDME(Virtual Database Engine)來完成,總地來說,就是將SQL語(yǔ)句先翻譯成一種專門設(shè)計(jì)的語(yǔ)言,然后下層再調(diào)用平臺(tái)相關(guān)的系統(tǒng)API接口,完成相應(yīng)的功能。
SQLite的源代碼由96個(gè)C語(yǔ)言文件(.c和.h)組成,在編譯之前會(huì)由Makefile生成一個(gè)完整的文件,即為可以在官方網(wǎng)站上下載的sqlite3.c和sqlite3.h等文件,然后編譯形成所需要的庫(kù)或者可執(zhí)行文件。
圖1給出了SQLite的主要模塊及相互之間的關(guān)系,以下將分別介紹各個(gè)部分的功能。
(1)接口(Interface)
SQLite庫(kù)提供的對(duì)外不調(diào)用的接口大多數(shù)都在main.c、legacy.c和vdbeapi.c中,其他一些散布在源代碼的不同部分。對(duì)接口的查詢可以在文檔中找到詳細(xì)的介紹。為了避免命名上的沖突,所有外部可以調(diào)用的接口都以sqlite3_開頭[3]。
(2)SQL編譯器(SQL Compiler)
這是一個(gè)比較完整的編譯器構(gòu)架,分別完成詞法分析、語(yǔ)法分析和中間代碼生成。詞法分析器(Tokenizer)由C語(yǔ)言實(shí)現(xiàn),包含在tokenize.c中;語(yǔ)法分析器(Parser)由Lmon LALR(1)生成,和YACC/BISON類似,不兼容,但是生成的代碼是可重入且線程安全的,代碼包含在parse.c中;代碼生成器(Code Generator)生成虛擬機(jī)執(zhí)行的中間代碼,包含的文件相對(duì)較多,例如select.c、update.c等,大多和SQL命令同名對(duì)應(yīng)。
(3)虛擬機(jī)VM(Virtual Machine)
代碼生成器生成的中間代碼會(huì)通過VM執(zhí)行。這部分后面會(huì)有更詳細(xì)的分解。
(4)B-Tree(B-樹)
數(shù)據(jù)庫(kù)在磁盤上的操作都是通過B-樹的,對(duì)應(yīng)于數(shù)據(jù)庫(kù)中的每一個(gè)表或者索引都會(huì)有相應(yīng)的B-樹。實(shí)現(xiàn)和接口分別在btree.c和btree.h中[4]。
(5)頁(yè)緩存(Page Cache)
數(shù)據(jù)的讀寫都以Chunk為單位進(jìn)行,這樣可以提高效率。頁(yè)緩存負(fù)責(zé)這部分工作,同時(shí)提供了回滾(rollback)等功能,并對(duì)數(shù)據(jù)庫(kù)文件進(jìn)行管理。實(shí)現(xiàn)和接口分別在pager.c和pager.h中。
(6)系統(tǒng)接口(OS Interface)
SQLite提供了一個(gè)系統(tǒng)抽象層,定義在os.h中。每個(gè)支持的平臺(tái)有自己對(duì)應(yīng)的實(shí)現(xiàn)文件,例如os_uinx.c和os_win.c(及相應(yīng)的頭文件os_unix.h和os_win.h)。
(7)功能和測(cè)試(Utility和Test Code)
2 VDBE框架及關(guān)鍵源碼分析
虛擬數(shù)據(jù)庫(kù)引擎VDBE(Virtual Database Engine)居于SQLite數(shù)據(jù)庫(kù)的核心部分。從整個(gè)SQLite的構(gòu)架可以看出,它處在整個(gè)系統(tǒng)的中間部分:前端代碼完成對(duì)SQL語(yǔ)言的編譯,相當(dāng)于簡(jiǎn)化版本的一個(gè)編譯器;后端完成物理上的操作,即利用B-Tree和Pager對(duì)物理硬盤上的數(shù)據(jù)進(jìn)行實(shí)際的操作。VDBE完成了這個(gè)層次上的抽象鏈接。
整個(gè)虛擬數(shù)據(jù)庫(kù)引擎(VDBE)由若干個(gè)C語(yǔ)言文件組成,主題實(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庫(kù)外的應(yīng)用程序,如sqlite3_bind系列函數(shù))使用的一些結(jié)構(gòu)。vdbemen.c 實(shí)現(xiàn)了在vdbe的存儲(chǔ)管理。
對(duì)于用戶的SQL語(yǔ)句,編譯器會(huì)生成一個(gè)虛擬機(jī)實(shí)例。虛擬機(jī)實(shí)例在內(nèi)部和外部是不同的。對(duì)內(nèi)看到的是一個(gè)vdbe結(jié)構(gòu)的實(shí)例,這個(gè)結(jié)構(gòu)定義在vdbeInt.h中,代碼如下:
struct Vdbe {
sqlite3 *db; /* 數(shù)據(jù)庫(kù)連接 */
Op *aOp; /* 保存虛擬機(jī)的空間 */
… /* 其他指令 */
int nOp; /* 生成的指令的條數(shù) */
char *zSql; /* SQL語(yǔ)句 */
… /* 其他指令 */
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語(yǔ)句在生成VDBE程序后,每條指令包含了一個(gè)操作碼(opcode)和至多5個(gè)操作數(shù)(operands:P1、P2、P3、P4和P5)。其中:
(1)P1、P2、P3都是32 bit的帶符號(hào)整數(shù),它們通常引用的是寄存器。
(2)P2在所有的有跳轉(zhuǎn)功能的指令中表示目的地址。例如上面的第2條指令將會(huì)跳轉(zhuǎn)到第10條指令,然后順序執(zhí)行。
(3)P4可以是32 bit或者64 bit的帶符號(hào)整型數(shù)據(jù)、字符串、BLOB數(shù)據(jù)(二進(jìn)制大對(duì)象)、函數(shù)指針等其他多樣的對(duì)象。
(4)P5通常是無(wú)符號(hào)的字符,充當(dāng)?shù)氖菢?biāo)識(shí)位。
在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是無(wú)符號(hào)字符型 */
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()會(huì)觸發(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ù)庫(kù)連接實(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ù),雖然重要,但是代碼的原理非常簡(jiǎn)單,就是一系列的switch-case語(yǔ)句。在相應(yīng)的case情況下,會(huì)執(zhí)行相應(yīng)的底層代碼,進(jìn)行數(shù)據(jù)庫(kù)的磁盤操作。
3 實(shí)驗(yàn)
3.1 數(shù)據(jù)庫(kù)編程接口
SQLite的編程模型比較簡(jiǎn)單,下面的例子給出了一個(gè)基本的框架。
#include "sqlite3.h"
#include <stdlib.h>
int main(int argc, char **argv)
{
char *file = "./test.db";/* 數(shù)據(jù)庫(kù)文件 */
sqlite3 *db = NULL; /* 數(shù)據(jù)庫(kù)連接實(shí)例 */
int rc = 0; /* 返回值 */
sqlite3_initialize(); /* 初始庫(kù) */
rc= sqlite3_open_v2(file, &db,
SQLITE_OPEN_READWRITE, NULL);
/* 準(zhǔn)備SQL語(yǔ)句,生成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語(yǔ)句提交給SQL編譯器,編譯成VDBE指令程序,sqlite3_step()將驅(qū)動(dòng)VDBE執(zhí)行指令程序。
從應(yīng)用上來說,這僅僅是最簡(jiǎn)單的數(shù)據(jù)庫(kù)應(yīng)用框架,更多的接口信息可以查看官方的文檔。
3.2 VDBE程序分析
在官方提供的下載中,有編譯好的命令行可執(zhí)行程序,可以作為完全的SQLite數(shù)據(jù)庫(kù)管理工具。同時(shí),它也考慮了一些Debug和Test功能,可以利用它們深入了解SQLite的內(nèi)部機(jī)制??梢岳肧QLite命令行程序中的explain命令查看由代碼生成器生成的中間代碼的形式,這只需要在相應(yīng)的SQL代碼前面加上explain就可以了。如以搜索的命令行顯示(如圖2所示,箭頭表示實(shí)際執(zhí)行順序):
圖2中,“addr”列是虛擬機(jī)的地址編號(hào),并不是指令執(zhí)行的順序,由于跳轉(zhuǎn)指令的存在,用箭頭標(biāo)示出了指令運(yùn)行的實(shí)際順序,也可以在SQLite編譯時(shí)指定相應(yīng)的選項(xiàng),然后利用指令“pragma vdbe_trace=on;”詳細(xì)地看到指令的運(yùn)行過程和堆棧的變化情況。
指令0~指令12都是對(duì)SQLite數(shù)據(jù)庫(kù)內(nèi)部的準(zhǔn)備:由指令1跳轉(zhuǎn)到指令10,指令10(Transaction)開始一個(gè)事務(wù),指令11(VerifyCookie)在執(zhí)行一個(gè)指令前檢查數(shù)據(jù)庫(kù)模式是否發(fā)生了變化,當(dāng)發(fā)生了變化時(shí)要重置,指令12(TableLock)將要讀的數(shù)據(jù)庫(kù)表鎖起來,指令13(Goto)跳轉(zhuǎn)到指令2。
從指令2開始是實(shí)際的對(duì)數(shù)據(jù)庫(kù)的操作了。指令2(OpenRead)會(huì)打開一個(gè)數(shù)據(jù)庫(kù)表的只讀游標(biāo),P1作為這個(gè)游標(biāo)的標(biāo)志,P2是打開的數(shù)據(jù)庫(kù)表的根頁(yè)(root page),P3==0表明是主數(shù)據(jù)庫(kù),P4表明數(shù)據(jù)庫(kù)有兩列,P5說明是以P2的值作為根頁(yè)。(OpenRead指令的各個(gè)操作數(shù)還可以有其他含義,這里只是針對(duì)這條SQL語(yǔ)句的解釋,請(qǐng)查看技術(shù)文檔。)指令3(Rewind)~指令7(Next)完成了對(duì)所有查詢數(shù)據(jù)的遍歷。指令8(Close)關(guān)閉游標(biāo),指令9(Halt)結(jié)束這個(gè)VDBE程序。
VDBE對(duì)上層提供的就是這樣的接口,而對(duì)下層將是調(diào)用相應(yīng)的接口實(shí)現(xiàn)相應(yīng)的功能,并由此完成模塊上的解耦合。
由VDBE的定義、代碼分析及以上的實(shí)驗(yàn),可以總結(jié)出SQLite的整體構(gòu)架:
外部調(diào)用SQLite接口函數(shù)sqlite3_prepare(), SQL語(yǔ)句通過SQL編譯器生成對(duì)應(yīng)的VDBE指令程序;
內(nèi)部調(diào)用sqlite3_step()驅(qū)動(dòng),內(nèi)部執(zhí)行sqlite3VdbeEx-
ec(),switch-case語(yǔ)句執(zhí)行相應(yīng)指令。底層通過B-Tree和Pager實(shí)現(xiàn)對(duì)磁盤數(shù)據(jù)庫(kù)文件的管理,如圖3所示。
在實(shí)際應(yīng)用中,可以設(shè)計(jì)一個(gè)面向應(yīng)用的指令集,利用程序虛擬機(jī)設(shè)計(jì)中間抽象層,提高平臺(tái)通用性。同時(shí)程序虛擬機(jī)也為語(yǔ)言虛擬機(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ù)庫(kù)SQLite及其應(yīng)用研究[J].沿海企業(yè)與科技,2010(10):45-47.
[4] 杜國(guó)祥,石俊杰.SQLite嵌入式數(shù)據(jù)庫(kù)的應(yīng)用[J].電腦編程技巧與維護(hù),2010(14):43-46.