SQLite學習筆記(十一)&&虛擬機原理


前言
      我們知道任何一種關系型數據庫管理系統都支持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的實現,我們知道虛擬機執行流程非常清晰,通過執行一系列指令流,就可以實現查詢,更新數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM