<tr id="5ry4k"></tr>
        1. <big id="5ry4k"></big>

          • WAP手機版 加入收藏  設為首頁
          文件數據恢復資料

          SQLite剖析之內核核心資料研究-

          時間:2016-10-3 11:47:57   作者:宇陽成都數據恢復中心   來源:本站原創   閱讀:2200   評論:0
          內容摘要:  先從全局的角度把握SQLite內核各個模塊的設計和功能。SQLite采用了層次化、模塊化的設計,而這些使得它的可擴展性和可移植性非常強。而且SQLite的架構與通用DBMS的結構差別不是很大,所以它對于理解通用DBMS具有重要意義。SQLite的內核總的來說分為三個部分,虛擬...

           

           

            先從全局的角度把握SQLite內核各個模塊的設計和功能。SQLite采用了層次化、模塊化的設計,而這些使得它的可擴展性和可移植性非常強。而且SQLite的架構與通用DBMS的結構差別不是很大,所以它對于理解通用DBMS具有重要意義。SQLite的內核總的來說分為三個部分,虛擬機(Virtual Machine)、Back-end(后端)和compiler(編譯器)。

          1、虛擬機(Virtual Machine)
          VDBE是SQLite的核心,它的上層模塊和下層模塊本質上都是為它服務的。它的實現位于vbde.c, vdbe.h, vdbeapi.c, vdbeInt.h和vdbemem.c幾個文件中。它通過底層的基礎設施B+Tree執行由編譯器(Compiler)生成的字節代碼,這種字節代碼程序語言(bytecode programming lauguage)是為了進行查詢,讀取和修改數據庫而專門設計的。
          字節代碼在內存中被封裝成sqlite3_stmt對象(內部叫做Vdbe,見vdbeInt.h),Vdbe(或者說statement)包含執行程序所需要的一切:
          a)    a bytecode program
          b)    names and data types for all result columns
          c)    values bound to input parameters
          d)    a program counter
          e)    an execution stack of operands
          f)    an arbitrary amount of "numbered" memory cells
          g)    other run-time state information (such as open BTree objects, sorters, lists, sets)

          字節代碼和匯編程序十分類似,每一條指令由操作碼和三個操作數構成:<opcode, P1, P2, P3>。Opcode為一定功能的操作碼,為了理解,可以看成一個函數。P1是32位的有符號整數,p2是31位的無符號整數,它通常是導致跳轉(jump)的指令的目標地址(destination),當然這里有其它用途;p3為一個以null結尾的字符串或者其它結構體的指針。和C API不同的是,VDBE操作碼經常變化,所以不應該用字節碼寫程序。
          下面的幾個C API直接和VDBE交互:
          • sqlite3_bind_xxx() functions
          • sqlite3_step()
          • sqlite3_reset()
          • sqlite3_column_xxx() functions
          • sqlite3_finalize()

          為了有個感性,下面看一個具體的字節碼程序:

          復制代碼
          sqlite> .m col
          sqlite> .h on
          sqlite> .w 4 15 3 3 15
          sqlite> explain select * from episodes;
          addr  opcode           p1   p2   p3
          ----  ---------------  ---  ---  ---------------
          0     Goto             0    12
          1     Integer          0    0
          2     OpenRead         0    2    # episodes
          3     SetNumColumns    0    3
          4     Rewind           0    10
          5     Recno            0    0
          6     Column           0    1
          7     Column           0    2
          8     Callback         3    0
          9     Next             0    5
          10    Close            0    0
          11    Halt             0    0
          12    Transaction      0    0
          13    VerifyCookie     0    10
          14    Goto             0    1
          15    Noop             0    0
          復制代碼

          1.1、棧(Stack)
          一個VDBE程序通常由不同完成特定任務的段(section)構成,每一個段中,都有一些操作棧的指令。這是由于不同的指令有不同個數的參數,一些指令只有一個參數;一些指令沒有參數;一些指令有好幾個參數,這種情況下,三個操作數就不能滿足。
          考慮到這些情況,指令采用棧來傳遞參數。(注:從匯編的角度來看,傳遞參數的方式有好幾種,比如:寄存器,全局變量,而堆棧是現代語言常用的方式,它具有很大的靈活性)。而這些指令不會自己做這些事情,所以在它們之前,需要其它一些指令的幫助。VDBE把計算的中間結果保存到內存單元(memory cells)中,其實,堆棧和內存單元都是基于Mem(見vdbeInt.h)數據結構(注:這里的棧、內存單元都是虛擬的,有位計算機科學家說過:計算機科學中90%以上的科學都是虛擬化問題。OS本質上也是虛擬機,而SQLite也同樣用到虛擬化,比如后面討論的OS interface模塊)。

          1.2、程序體(Program Body)
          上面的程序是一個打開episodes表的過程。
          第一條指令:Integer是為第二條指令作準備的,也就是把第二條指令執行需要的參數壓入堆棧,OpenRead從堆棧中取出參數值然后執行。SQLite可以通過ATTACH命令在一個連接中打開多個數據庫文件,每當SQLite打開一個數據,它就為之賦一個索引號(index),main database的索引為0,第一個數據庫為1,依次如此。Integer指令數據庫索引的值壓入棧,而OpenRead從中取出值,并決定打開哪個數據庫,來看看SQLite文檔中的解釋:
               Open a read-only cursor for the database table whose root page is P2 in a database file.
            The database file is determined by an integer from the top of the stack. 0 means the main database and 1 means the database used for temporary tables. Give the new cursor an identifier of P1. The P1 values need not be contiguous but all P1 values should be small integers. It is an error for P1 to be negative.
               If P2==0 then take the root page number from off of the stack.
               There will be a read lock on the database whenever there is an open cursor. If the database was unlocked prior to this instruction then a read lock is acquired as part of this instruction. A read lock allows other processes to read the database but prohibits any other process from modifying the database. The read lock is released when all cursors are closed. If this instruction attempts to get a read lock but fails, the script terminates with an SQLITE_BUSY error code.
               The P3 value is a pointer to a KeyInfo structure that defines the content and collating sequence of indices. P3 is NULL for cursors that are not pointing to indices. 

          再來看看SetNumColumns指令設置游標到將指向的行。P1為游標的索引(這里為0,剛剛打開),P2為列的數目,episodes表有三列。
          繼續Rewind指令,它將游標重新設置到表的開始,它會檢查表是否為空(即沒有記錄),如果沒有記錄,它會導致指令指針跳到P2指定的指令處。在這里,P2為10,即Close指令。一旦Rewind設置游標,接下來就執行5-9這幾條指令,它們的主要功能是遍歷結果集,Recno把由游標P1指定的記錄的關鍵字壓入堆棧。Column指令從由P1指定的游標,P2指定的列取值。5,6,7三條指令分別把id(primary key),season和name字段的值壓入棧。接下來,Callback指令從棧中取出三個值(P1),然后形成一個記錄數組,存儲在內存單元中(memory cell)。Callback會停止VDBE的操作,把控制權交給sqlite3_stemp(),該函數返回SQLITE_ROW。
          SQLite剖析之內核核心資料研究-
          一旦VDBE創建了記錄結構,我們就可以通過sqlite3_column_xxx() functions從記錄結構的域內取出值。當下次調用sqlite3_step()時,指令指針會指向Next指令,而Next指令會把游標移向下一行,如果有其它的記錄,它會跳到由P2指定的指令,在這里為指令5,創建一個新的記錄結構,一直循環,直到結果集的最后。Close指令會關閉游標,然后執行Halt指令,結束VDBE程序。

          1.3、程序開始與停止
          其余的指令中,Goto指令是一條跳轉指令,跳到P2處,即第12條指令。指令12是Transaction,它開始一個新的事務;然后執行VerifyCookie,它的主要功能是檢查VDBE程序編譯后,數據庫模式是否改變(即是否進行過更新操作),這在SQLite中是一個很重要的概念,在SQL被sqlite3_prepare()編譯成VDBE代碼至程序調用sqlite3_step()執行字節碼的這段時間,另一個SQL命令可能會改變數據庫模式(such as ALTER TABLE, DROP TABLE, or CREATE TABLE)。一旦發生這種情況,之前編譯的statement就會變得無效,數據庫模式信息記錄在數據庫文件的根頁面中。類似,每一個statement都有一份用來比較的備份,是在編譯時刻對該模式的備份,VerifyCookie的功能就是檢查它們是否匹配,如果不匹配,將采取相關操作。
          SQLite剖析之內核核心資料研究-

          如果兩者匹配,會執行下一條指令Goto;它會跳到程序的主要部分,即第一條指令,打開表讀取記錄。這里有兩點值得注意:
          (1)Transaction指令自己不會獲取鎖(The Transaction instruction doesn’t acquire any locks in itself)。它的功能相當于BEGIN,而實際是由OpenRead指令獲取share lock的。當事務關閉時釋放鎖,這取決于Halt指令,它會進行掃尾工作。
          (2)statement對象(VDBE程序)所需的存儲空間在程序執行前就已經確定。這是基于兩個重要事實:首先,棧的深度不會比指令的數目還多(通常少得多)。其次,在執行VDBE程序之前,SQLite可以計算出為分配資源所需要的內存。

          1.4、指令的類型(Instruction Types)
          每條指令都完成特定的任務,而且通常和別的指令有關。大體上來說,指令可分為三類:
          (1)Value manipulation:這類指令通常完成算術運算(比如:add、subtract、 divide),邏輯運算(比如:AND、OR)和字符串操作。
          (2)Data management:這類指令操作在內存和磁盤上的數據。內存指令進行棧操作或者在內存單元之間傳遞數據。磁盤操作指令控制B-tree和pager打開或操作游標,開始或結束事務等等。
          (3)Control flow:控制指令主要是移動指令指針。

          1.5、程序的執行(Program execution)
          VM解釋器是如何實現的?字節代碼是如何執行的?
          在vdbe.c文件中有一個很關鍵的函數:

          復制代碼
          //執行VDBE程序
          int sqlite3VdbeExec(
            Vdbe *p                    /* The VDBE */
          )
          該函數是執行VDBE程序的入口。來看看它的內部實現:
          
          /*從這里開始執行指令
          **pc為程序計數器(int)
          */
          for(pc=p->pc; rc==SQLITE_OK; pc++){
            //取得操作碼
            pOp = &p->aOp[pc];
            switch( pOp->opcode ){
            case OP_Goto: {             /* jump */
                CHECK_FOR_INTERRUPT;
                pc = pOp->p2 - 1;
                break;
               }
              … …
             }
          }
          復制代碼

          從這段代碼,我們大致可以推出VM執行的原理:VM解釋器實際上是一個包含大量switch語句的for循環,每一個switch語句實現一個特定的操作指令。

          2、B-tree和Pager
          B-Tree使得VDBE可以在O(logN)下查詢,插入和刪除數據,以及O(1)下雙向遍歷結果集。B-Tree不會直接讀寫磁盤,它僅僅維護著頁面(pager)之間的關系。當B-Tree需要頁面或者修改頁面時,它就會調用Pager。當修改頁面時,pager保證原始頁面首先寫入日志文件,當它完成寫操作時,pager根據事務狀態決定如何做。B-tree不直接讀寫文件,而是通過page cache這個緩沖模塊讀寫文件,對于性能是有重要意義的(這和操作系統讀寫文件類似,在Linux中,操作系統的上層模塊并不直接調用設備驅動讀寫設備,而是通過一個高速緩沖模塊調用設備驅動讀寫文件,并將結果存到高速緩沖區)。

          2.1、數據庫文件格式(Database File Format)
          數據庫中所有的頁面都按從1開始順序標記。一個數據庫由許多B-tree構成——每一個表和索引都有一個B-tree(索引采用B-tree,而表采用B+tree,這主要是表和索引的需求不同以及B-tree和B+tree的結構不同決定的:B+tree的所有葉子節點包含了全部關鍵字信息,而且可以有兩種順序查找;而B-tree更適合用來作索引)。所有表和索引的根頁面都存儲在sqlite_master表中。
          數據庫中第一個頁面(page 1)有點特殊,page 1的前100個字節包含一個描述數據庫文件的特殊的文件頭。它包括庫的版本,模式的版本,頁面大小,編碼等所有創建數據庫時設置的參數。這個特殊的文件頭的內容在btree.c中定義,page 1也是sqlite_master表的根頁面。

          2.2、頁面重用及回收(Page Reuse and Vacuum )
          SQLite利用一個空閑列表(free list)進行頁面回收。當一個頁面的所有記錄都被刪除時,就被插入到該列表。當運行VACUUM命令時,會清除空閑列表的頁面,此時數據庫會縮小,本質上它是在新的文件中重新建立數據庫,而所有使用的頁再都被拷貝過去,而空閑列表頁面卻不會,結果就是一個新的、變小的數據庫。當數據庫的autovacuum開啟時,SQLite不會使用free list,而且在每一次commit時自動壓縮數據庫。

          2.3.1、B-Tree記錄
          B-tree頁面由B-tree記錄組成,B-tree記錄也叫做payloads。每一個B-tree記錄(payload)有兩個域:關鍵字域(key field)和數據域(data field)。Key field就是rowid的值,或者數據庫中表的關鍵字的值。從B-tree的角度,data field可以是任何無結構的數據。數據庫的記錄就保存在這些data fields中。B-tree的任務就是排序和遍歷,它最需要的是關鍵字。Payloads的大小是不定的,這與內部的關鍵字和數據域有關,當一個payload太大不能存在一個頁面內,便保存到多個頁面。

          2.3.2、B+Tree

          B+Tree按關鍵字排序,所有的關鍵字必須唯一。表采用B+tree,內部頁面不包含數據,如下:
          SQLite剖析之內核核心資料研究-

           B+tree中根頁面(root page)和內部頁面(internal pages)都是用來導航的,這些頁面的數據域都是指向下級頁面的指針,僅僅包含關鍵字。所有的數據庫記錄都存儲在葉子頁面(leaf pages)內。在葉節點一級,記錄和頁面都是按照關鍵字的順序的,所以B+tree可以水平方向遍歷,時間復雜度為O(1)。

          2.4、記錄和域(Records and Fields)
          位于葉節點頁面的數據域的記錄由VDBE管理,數據庫記錄以二進制的形式存儲,但有一定的數據格式。記錄格式包括一個邏輯頭(logical header)和一個數據區(data segment),邏輯頭區包括header的大小(hsize)和一個數據類型數組(T1~TN),數據類型即在data segment的數據的類型,如下:

           SQLite剖析之內核核心資料研究-

          2.5、層次數據組織(Hierarchical Data Organization) 

          SQLite剖析之內核核心資料研究-

          從上往下,數據越來越無序,從下向上,數據越來越結構化.

          2.6、B-Tree API
          B-Tree模塊有自己的API,它可以獨立于C API使用。另一個特點就是它支持事務。由pager處理的事務、鎖和日志都是為B-tree服務的。根據功能可以分為以下幾類:

          2.6.1、訪問事務函數
          sqlite3BtreeOpen: Opens a new database file. Returns a B-tree object.
          sqlite3BtreeClose: Closes a database.
          sqlite3BtreeBeginTrans: Starts a new transaction.
          sqlite3BtreeCommit: Commits the current transaction.
          sqlite3BtreeRollback: Rolls back the current transaction.
          sqlite3BtreeBeginStmt: Starts a statement transaction.
          sqlite3BtreeCommitStmt: Commits a statement transaction.
          sqlite3BtreeRollbackStmt: Rolls back a statement transaction.

          2.6.2、表函數
          sqlite3BtreeCreateTable: Creates a new, empty B-tree in a database file. 
          sqlite3BtreeDropTable: Destroys a B-tree in a database file.
          sqlite3BtreeClearTable: Removes all data from a B-tree, but keeps the B-tree intact.

          2.6.3、游標函數(Cursor Functions)
          sqlite3BtreeCursor: Creates a new cursor pointing to a particular B-tree. 
          sqlite3BtreeCloseCursor: Closes the B-tree cursor.
          sqlite3BtreeFirst: Moves the cursor to the first element in a B-tree.
          sqlite3BtreeLast: Moves the cursor to the last element in a B-tree.
          sqlite3BtreeNext: Moves the cursor to the next element after the one it is currently pointing to.
          sqlite3BtreePrevious: Moves the cursor to the previous element before the one it is currently pointing to.       
          sqlite3BtreeMoveto: Moves the cursor to an element that matches the key value passed  in as a parameter. 

          2.6.4、記錄函數(Record Functions)
          sqlite3BtreeDelete: Deletes the record that the cursor is pointing to.
          sqlite3BtreeInsert: Inserts a new element in the appropriate place of the B-tree.
          sqlite3BtreeKeySize: Returns the number of bytes in the key of the record that the cursor is pointing to.
          sqlite3BtreeKey: Returns the key of the record the cursor is currently pointing to.
          sqlite3BtreeDataSize: Returns the number of bytes in the data record that the cursor is currently pointing to. 
          sqlite3BtreeData: Returns the data in the record the cursor is currently pointing to.             

          2.6.5、配置函數(Configuration Functions)
          sqlite3BtreeSetCacheSize: Controls the page cache size as well as the synchronous writes (as defined in the synchronous pragma).
          sqlite3BtreeSetSafetyLevel: Changes the way data is synced to disk in order to increase or decrease how well the database resists damage due to OS crashes and power failures. 
                     Level 1 is the same as asynchronous (no syncs() occur and there is a high probability of damage). This is the equivalent to pragma synchronous=OFF.
                     Level 2 is the default. There is a very low but non-zero probability of damage. This is the equivalent to pragma synchronous=NORMAL.
                     Level 3 reduces the probability of damage to near zero but with a write performance reduction. This is the equivalent to pragma synchronous=FULL. 
          sqlite3BtreeSetPageSize: Sets the database page size.
          sqlite3BtreeGetPageSize: Returns the database page size.
          sqlite3BtreeSetAutoVacuum: Sets the autovacuum property of the database.
          sqlite3BtreeGetAutoVacuum: Returns whether the database uses autovacuum.
          sqlite3BtreeSetBusyHandler: Sets the busy handler.           

          2.7、實例分析
          sqlite3_open的具體實現過程如下:

          SQLite剖析之內核核心資料研究-

          由上圖可知,SQLite的所有IO操作,最終都轉化為操作系統的系統調用(DBMS建立在OS上)。同時也可以看到SQLite的實現非常層次化,模塊化,使得SQLite更易擴展,可移植性非常強。

          3、編譯器(Compiler)
          3.1、分詞器(Tokenizer)
          接口把要執行的SQL語句傳遞給Tokenizer,Tokenizer按照SQL的詞法定義把它切分成一個一個的詞,并傳遞給分析器(Parser)進行語法分析。分詞器是手工寫的,主要在Tokenizer.c中實現。

          3.2、分析器(Parser)
          SQLite的語法分析器是用Lemon(一個開源的LALR(1)語法分析器的生成器)生成的,生成的文件為parser.c。
          一個簡單的語法樹:

           SELECT rowid, name, season FROM episodes WHERE rowid=1 LIMIT 1

          SQLite剖析之內核核心資料研究-

          3.3、代碼生成器(Code Generator)
          代碼生成器是SQLite中最龐大,最復雜的部分。它與Parser關系緊密,根據語法分析樹生成VDBE程序執行SQL語句的功能。由諸多文件構成:select.c,update.c,insert.c,delete.c,trigger.c,where.c等文件。這些文件生成相應的VDBE程序指令,比如SELECT語句就由select.c生成。
          下面是一個讀操作中打開表的代碼生成實現

          復制代碼
          /* Generate code that will open a table for reading.
          */
          void sqlite3OpenTableForReading(
            Vdbe *v,        /* Generate code into this VDBE */
            int iCur,       /* The cursor number of the table */
            Table *pTab     /* The table to be opened */
          ){
            sqlite3VdbeAddOp(v, OP_Integer, pTab->iDb, 0);
            sqlite3VdbeAddOp(v, OP_OpenRead, iCur, pTab->tnum);
            VdbeComment((v, "# %s", pTab->zName));
            sqlite3VdbeAddOp(v, OP_SetNumColumns, iCur, pTab->nCol);
          }
          復制代碼

          Sqlite3vdbeAddOp函數有三個參數:(1)VDBE實例(它將添加指令),(2)操作碼(一條指令),(3)兩個操作數。

          3.4、查詢優化
          代碼生成器不僅負責生成代碼,也負責進行查詢優化。主要的實現位于where.c中,生成的WHERE語句塊通常被其它模塊共享,比如select.c,update.c以及delete.c。這些模塊調用sqlite3WhereBegin()開始WHERE語句塊的指令生成,然后加入它們自己的VDBE代碼返回,最后調用sqlite3WhereEnd()結束指令生成,如下:
           

          SQLite剖析之內核核心資料研究-



          相關評論
          不良信息舉報中心成都網警網警110報警服務AAA級互聯網行業信用360網站安全檢測

          數據恢復QQ交流群:378664983    站長QQ:958754010


          蜀ICP備14015947號-2


          麻豆人人妻人人妻人人片Av_碰国产免费公开视频_99精品成人片免费毛片无码_国产91视频天天爽夜夜爽