前言 我們知道任何一種關係型資料庫管理系統都支持SQL(Structured Query Language),相對於文件管理系統,用戶不用關心數據在資料庫內部如何存取,也不需要知道底層的存儲結構,熟悉SQL,就能熟練使用資料庫。SQL的引入,使得資料庫系統需要將SQL轉換為內部的數據結構,然後與.....
前言
我們知道任何一種關係型資料庫管理系統都支持SQL(Structured Query Language),相對於文件管理系統,用戶不用關心數據在資料庫內部如何存取,也不需要知道底層的存儲結構,熟悉SQL,就能熟練使用資料庫。SQL的引入,使得資料庫系統需要將SQL轉換為內部的數據結構,然後與底層的存儲結構打通,達到用戶存取數據的目的。所謂的SQL對應的數據結構,我們通常稱之為執行計劃,每個SQL執行前,都需要生成執行計劃,然後執行。SQL如何變化到等價的執行計劃?我們熟悉的資料庫,Oracle,Sqlserver,Mysql等通過對SQL進行詞法分析,語法分析,語義分析,生成執行計劃等步驟,最終生成執行計劃,這個計劃一般是一個複雜的數據結構。SQLite也通過以上幾步生成執行計劃,但特別的是,SQLite的執行計劃是一串指令流,這個指令流是由代碼生成器生成,代碼生成器將語法樹翻譯成一種SQLite專用的內部指令,通過虛擬機來解析執行。指令流相當於SQL與虛擬機的中介,由於指令流是扁平的,SQLite提供方法(PRAGMA vdbe_trace=ON)讓用戶可以看到執行SQL的每一條指令,清楚地知道數據在SQLite內部是如何流轉的。本文主要講SQLite的虛擬機(Virtual Database Engine,簡稱VDBE)的原理以及相關的內部指令。
虛擬機
所謂虛擬機是指對真實電腦資源環境的一個抽象,它為語言程式提供了一套完整的電腦介面。比如我們熟悉的JAVA語言,我們在跑JAVA程式時,其實是運行在JVM(JAVA Virtual Machine)環境中,所有的JAVA程式首先被編譯為.class類文件,這種類文件在虛擬機上執行,也就是說class文件並不與操作系統指令對應,而是經過虛擬機間接與操作系統交互。SQLite的虛擬機也是如此,編譯SQL產生的指令流只有SQLite虛擬機(Virtual Database Engine,簡稱VDBE)能識別,由虛擬機與底層的存儲(表,索引)交互,這種方式使得SQLite內部模塊分工非常清晰,耦合度很低。如下圖所示,我們可以看到VDBE的位置,它處於編譯器與Btree模塊的中間,是SQLite的核心,負責SQL到數據存取的交互。後面我提到的虛擬機都是指SQLite虛擬機(Virtual Machine,VM),VM模塊將底層存儲看作是記錄維度的文件系統,通過執行指令流,來讀寫表上的記錄。
VDBE數據結構和API
struct Vdbe{
sqlite3 *db; /* The database connection that owns this statement */
Op *aOp; /* Space to hold the virtual machine's program */
int nOp; /* Number of instructions in the program */
Mem **apArg; /* Arguments to currently executing user function */
Parse *pParse; /* Parsing context used to create this Vdbe */
int pc; /* The program counter */
Mem *aMem; /* The memory locations */
int nMem; /* Number of memory locations currently allocated */
Mem *aColName; /* Column names to return */
u16 nResColumn; /* Number of columns in one row of the result set */
char *zSql; /* Text of the SQL statement that generated this */
}
我從源碼中選取了比較重要的對象,主要包括資料庫對象(db),指令流對象(aOp,nOp),綁定輸入的參數值(apArg),解析SQL的對象(pParse),指令流計數器(pc),存儲臨時變數的寄存器(aMem,nMem),返回結果集集的列名和列信息(aColName,nResColumn)以及執行的產生虛擬機指令的SQL(zSql)等。這些基本就是虛擬機對象的全部,有指令,有寄存器,有指令計數器,與彙編語言非常相似,只不過VDBE裡面的指令是sqlite內部識別的指令,而彙編語言指令是與機器指令對應的。如果想瞭解VDBE所有的對象,可以參考vdbeInt.h中關於該結構的定義,另外關於sqlite3結構和Parse結構可以參考sqliteInt.h文件。
瞭解了Vdbe數據結構,我們再來看看我們平時常用的API是如何與VDBE交換數據的。通常我們要執行一個語句,會執行如下幾個步驟。
1.調用sqlite3_prepare_*來編譯生成指令流,返回一個sqlite3_stmt對象,其實這個對象就是vdbe對象。
2.調用sqlite3_bind_*來將參數傳遞給vdbe,
3.調用sqlite3_step進行執行,這時候會啟動虛擬機執行一條條指令,直到遇到中斷或者停止指令為止
4.調用sqlite3_column_*來獲取上一步準備好的結果集
5.調用sqlite3_finalize,銷毀vdbe對象,結束這次執行。
此外我們還可能用到sqlite3_reset介面,這個介面將指令流回退到第一條指令,用戶可以調用sqlite3_step重新執行。有關API的詳細說明,可以參考文件vdbeapi.c。
虛擬機指令
虛擬機核心就是扁平化指令,SQLite定義了一系列指令語言,每個指令做一小部分動作,虛擬機通過執行一些列指令達到查詢,修改資料庫的目的。每一條指令包含一個操作符和5個操作數,形式如下:<opcode,P1,P2,P3,P4,P5>。P1,P2,P3是一個32位有符號整數,P1一般是游標編號,P2一般是指令需要跳轉的指令位置,P4是一個32位/64位整數,64位的浮點數,或者是指向字元串的指針,或者是二進位等,P5是一個無編號的字元。不是每條指令都使用了全部5個操作數,有的指令只需要2到3個操作數。後面一篇文章我會結合實例詳細講解指令的作用,以及對應操作數的含義。
虛擬機執行流程
虛擬機的核心流程在sqlite3VdbeExec函數中,我們調用sqlite3_step時就會調用到該函數。由於這個函數比較大,大概有6000行代碼,裡面包含了每條指令的執行過程,為了方便說明,我會簡化函數內容來說明這個函數的邏輯,抽象的代碼如下。從代碼流程來看,邏輯非常簡單,通過迴圈遍歷指令數組中的每條指令逐一執行,直到遇到中斷或終止指令為止。如果需要逐條瞭解每條指令的含義,還需要仔細閱讀代碼。
sqlite3VdbeExec(Vdbe *p)
{
Op *aOp = p->aOp; /* Copy of p->aOp */
Op *pOp = aOp; /* Current operation */
for(pOp=&aOp[p->pc]; rc==SQLITE_OK; pOp++){
switch(pOp->opcode){
case OP_Goto: //jump to P2指向的指令
{
pOp = &aOp[pOp->p2 - 1];
break;
}
case OP_Integer: // value P1 is written into register P2.
{
pOut = out2Prerelease(p, pOp);
pOut->u.i = pOp->p1;
break;
}
case OP_Real:
{
......
break;
}
case OP_Halt:
{
......
break;
}
...
}// end of switch
} // end of for
}
小結
本文介紹了SQLite虛擬機以及對應的指令流。通過介紹vdbe的存儲結構,我們瞭解到vdbe對象所包含的內容;通過介紹API,我們瞭解到API與虛擬機的關係;通過介紹函數sqlite3VdbeExec的實現,我們知道虛擬機執行流程非常清晰,通過執行一系列指令流,就可以實現查詢,更新數據。