SQLite是一款輕量、快速、跨平臺的嵌入式數據庫,是遵照ACID(注:ACID指數據庫事務正確執行的四個基本要素的縮寫。包含:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability))的關係型數據庫管理系統,它包含在一個相對小的C庫中。html
SQLite 的設計目標是簡單,從這種意義上說,SQLite 和其餘不少現代的 SQL 數據庫都不相同。SQLite 力求簡單,即便這致使了它的某些特徵偶爾執行效率比較低。它能夠很簡單地維護、定製、操做、管理和嵌入到 C 應用程序中。它使用簡單的技術實現了 ACID 特性。前端
SQLite 把全部文件存儲在一個單一的普通本地文件裏面,你能夠把它放在本地系統的任何目錄中。任何有讀取該文件權限的用戶均可以讀取數據庫裏面的全部內容。任何一個有當前目錄寫入權限的用戶,均可以對數據庫作任何修改。SQLite 使用另外一個單獨的日誌文件來保存事務恢復信息,用來防止事務停止或系統故障。你能夠用程序指令來改變 SQLite 庫的一些行爲。sql
SQLite 容許多個應用同時鏈接同一個數據庫。固然,它所支持的併發事務是有必定限制的:SQLite容許任何數量的併發讀取事務在數據庫上同時執行,但只容許一個寫入事務獨佔執行。 數據庫
本文從native和Frameworks兩個層面的實現來講明Android系統中SQLite的實現。編程
SQlite動態庫的軟件架構以下圖所示:後端
SQLite的實現大致上能夠劃分爲前端和後端兩大塊。數組
前端:前端預處理應用程序輸入的 SQL 語句和 SQLite 命令。它分析、優化這些語句(和命令),而後生成後端能夠解析的 SQLite 內部字節碼程序。前端實現了 sqlite3_prepare 接口方法。緩存
(1)編程接口(Interface) cookie
對數據庫使用者提供的接口方法。數據結構
(2)詞法分析器(The tokenizer)
分詞器把輸入的語句分割成記號。
(3)語法分析器(The parser)
詞法分析器經過分析詞法分析器產生的記號來肯定語句的結構,並生成解析樹。語法分析器還包括一個優化器來重構解析樹,並生成一個能夠產生高效率的字節碼(bytecode)程序的等效的解析樹。
(4)代碼生成器(The code generator)
代碼生成器遍歷解析樹,生成一個等效的字節碼程序。
後端:後端是一個解釋字節碼(bytecode)程序的引擎。後端作實際的數據庫讀寫工做。後端實現了 sqlite3_bind_*,sqlite3_step,sqlite3_column_*,sqlite3_reset 和 sqlite3_finalize 接口方法。
(1)虛擬機 (VM, The Virtual Machine)
虛擬機是內部字節碼(bytecode)語言的解釋程序。它運行字節碼(bytecode)程序來執行 SQL 語句的操做。它是數據庫數據的最終操做者。它把數據庫看做表和索引的集合,而表或索引則是一系列數據記錄的集合。
(2)B/B+-tree
B/B+ 樹把每個數據記錄組織到一個有序的樹形數據結構中;表和索引分別存放在 B+ 樹和 B 樹。此模塊幫助 VM 在樹中搜索、插入和刪除數據記錄。它也幫助 VM 建立新的樹,刪除舊的樹。
(3)頁面管理器(pager)
Pager 模塊在文件系統頂部實現了面向頁面的數據庫文件抽象。它管理內存中的由 B/B+ 樹使用的數據庫頁面緩存,另外,它也管理文件鎖和日誌記錄,以便實現事務的 ACID 特性。
(4)操做系統接口
操做系統接口模塊爲不一樣的操做系統抽象出了統一的接口。
本節介紹SQLite各模塊的實現方式。
SQLite把整個數據庫存儲在一個數據庫文件中。
爲了便於管理和讀寫數據庫,SQLite 把每個數據庫(包括內存數據庫(in-memory database))都劃分爲大小固定的頁面(page),頁面大小能夠是512~32768字節(默認1024字節,在數據庫文件建立以前能夠經過pragma命令修改頁大小)。數據庫文件能夠看做是頁面的數組,頁面的索引是從1開始,不是從0開始(頁面0會被看成NULL頁面,即不是一個實際存在的頁面),頁面索引最大是2^31-1。
頁面的類型能夠分爲4種:leaf、internal、overflow和free。
第1頁必定是internal類型的,並且第1頁的前100字節固定存放文件頭信息,它描述了整個數據庫的屬性。文件頭的格式以下:
Offset |
Size |
Description |
0 |
16 |
The header string: "SQLite format 3\000" |
16 |
2 |
The database page size in bytes. Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536. |
18 |
1 |
File format write version. 1 for legacy; 2 for WAL. |
19 |
1 |
File format read version. 1 for legacy; 2 for WAL. |
20 |
1 |
Bytes of unused "reserved" space at the end of each page. Usually 0. |
21 |
1 |
Maximum embedded payload fraction. Must be 64. |
22 |
1 |
Minimum embedded payload fraction. Must be 32. |
23 |
1 |
Leaf payload fraction. Must be 32. |
24 |
4 |
File change counter. |
28 |
4 |
Size of the database file in pages. The "in-header database size". |
32 |
4 |
Page number of the first freelist trunk page. |
36 |
4 |
Total number of freelist pages. |
40 |
4 |
The schema cookie. |
44 |
4 |
The schema format number. Supported schema formats are 1, 2, 3, and 4. |
48 |
4 |
Default page cache size. |
52 |
4 |
The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. |
56 |
4 |
The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. |
60 |
4 |
The "user version" as read and set by the user_version pragma. |
64 |
4 |
True (non-zero) for incremental-vacuum mode. False (zero) otherwise. |
68 |
4 |
The "Application ID" set by PRAGMA application_id. |
72 |
20 |
Reserved for expansion. Must be zero. |
92 |
4 |
|
96 |
4 |
注:考慮到跨平臺,數據庫文件的字節序是大頭(big-endian)的。
SQLite數據庫文件格式詳情參見https://www.sqlite.org/fileformat2.html。
操做系統接口抽象層是SQLite實現跨平臺的關鍵模塊,也叫虛擬文件系統(VFS),它對上層提供操做文件系統的統一接口方法,底層調用對應操做系統的接口方法實現具體的文件操做。
Pager是惟一訪問底層數據庫文件和日誌(journal)文件的模塊。它自己既不解析,也不修改頁面內容,而是把數據庫文件抽象成了基於頁面的可隨機訪問的緩存。上層模塊(B+-tree)老是使用Pager的提供接口方法讀寫數據庫文件,而不會直接訪問數據庫文件或日誌文件。
Pager的主要職責是管理頁面緩存,負責從數據庫讀入須要的頁面(爲了減小內存使用,頁面只在須要時讀入)。此外,爲了支持對上層透明的數據庫讀寫操做,Pager還封裝了其餘職責,包括:事務管理、數據管理、日誌管理和文件鎖管理。總的來講,Pager保證了數據庫存儲的持久性和事務操做的原子性。以下:
在上層模塊(B+-tree)看來,數據庫操做就是一系列的事務的執行,而不須要關心事務的事務執行是如何實現的,Pager會把事務執行細分爲獲取文件鎖、記錄日誌、讀寫數據庫文件等操做,下面對Pager的這幾個模塊的實現分別簡要介紹一下。
當SQLite打開一個數據庫,就會建立一個Pager來管理頁面緩存,若是同一個線程打開屢次數據庫,只會建立一個頁面緩存,若是多個線程打開同一個數據庫,則每一個線程會建立一個頁面緩存。
Pager經過一個哈希表來管理頁面緩存,哈希表開始時是空的,隨着數據庫的訪問Pager會不斷往裏面插入新的頁面,頁面緩存的結構以下:
Pager管理頁面緩存的原則是"按需讀取"和"延遲寫入"。
頁面緩存中緩存的頁面數量是有限的,當從數據庫文件讀取數據到頁面緩存而沒有空閒頁面時,Pager會根據LRU策略回收一個頁面(若是頁面有髒數據,會先寫入數據庫文件),而後再讀取數據到空閒出來的頁面中。
事務管理是保證數據庫同步的關鍵,SQLite依賴文件鎖和日誌來實現數據庫事務的ACID屬性,SQLite數據庫只支持串行事務,不支持事務的嵌套和存儲點(savepoint)。
SQLite執行的每一條SQL語句都必須放在事務中,讀取數據操做放在讀寫事務(read-or-write-transaction)中,寫入數據操做放在寫事務(write-transaction)中。應用程序並不須要爲每一條SQL語句啓動一個事務,SQLite會爲自動每一條獨立執行的SQL語句建立對應的事務並執行,這種由SQLite自動建立事務稱爲原子事務,或者系統級事務。
建立系統級事務的代價是比較昂貴的,尤爲是對頻繁寫數據庫的場景,緣由是如下幾點:
提升讀寫效率的辦法是使用用戶級事務,即顯示的啓動一個事務,再執行多條SQL語句,最後提交事務。
SQLite只支持串行的事務,因此應用程序在一個數據庫鏈接上只能執行一個事務,在事務內部再次啓動事務會獲得一個錯誤。
日誌是一個信息庫,用來在事務放棄(abort)、應用程序異常或者系統異常時恢復數據庫數據,它只能用來回滾事務操做。SQLite爲每一個數據庫文件使用一個日誌文件,文件名前綴和數據庫文件名相同並以-journal結尾。
SQLite同一時刻只容許一個寫事務執行,它會在執行時建立日誌文件,在提交後刪除日誌文件。在事務修改一個頁面以前,SQLite會把正在被修改的頁面寫入日誌文件,以備未來回復使用,日誌記錄的格式以下:
SQLite使用鎖機制來保證事務的串行執行,它在事務開始執行時獲取鎖,在事務執行完畢或者放棄後釋放鎖。SQLite使用的是數據庫級別的鎖,而不是行、表或頁面級別的鎖。
在事務看來,數據庫文件有五種鎖狀態:NOLOCK(未鎖定),SHARED(共享只讀),RESERVED(正在讀取,即將寫入,能夠獲取SHARED鎖,只能有一個),PENDING(等待寫入,禁止獲取SHARED鎖,只能有一個),EXLUSIVE(只能寫入,禁止獲取SHARED鎖,只能有一個)。
各類鎖狀態的轉換時序以下:
若是有多個線程同時申請RESERVED鎖(它們已經持有SHARED鎖),獲得RESERVED鎖的線程會等待其餘線程釋放SHARED鎖才能繼續獲取EXLUSIVE鎖,此時另外一個線程若是等待獲取RESERVED鎖可能會引發死鎖。SQLite爲避免這種死鎖,使用的方式是非阻塞方式獲取鎖,即另外一個線程嘗試獲取幾回RESERVED鎖,若是獲取不到就會放棄,並返回SQLITE_BUSY錯誤。
鎖的具體實現依賴操做系統提供的文件鎖接口。
在Pager提供的面向頁面的數據緩存基礎上,SQLite使用B+-tree來組織全部數據記錄,每一張表對應一個B+-tree。而數據庫表的索引則被存放在B-tree中。
B-tree也叫多路搜索樹,B-tree的B表明是的是"balanced",balanced的意思就是全部葉子節點都在同一層次。B-tree的全部搜索信息和數據均可以保存在中間節點和葉子節點,它通常用於組織索引信息,使用B-tree結構能夠顯著減小定位記錄時所經歷的中間過程,從而加快查找速度。
B+-tree是B-tree的變體,它的葉子節點只用來存放數據,它的中間節點只存放搜索信息和子節點指針。
這兩種樹結構的中間節點能夠保存的子節點指針數量是可變的,且中間節點的數據遵循以下原則:
B+-tree中間節點的數據結構以下:
SQLite中的B+-tree是經過分配根節點來建立的,根節點不能重複分配,每一個B+-tree都是經過其根節點的頁面編號來標識的。頁面編號存放在主目錄表中,主目錄表(master catalog table)的根節點存放在數據庫文件的第1個頁面中。
B+-tree的結構以下:
SQLite把各樹節點存(包括中間節點和葉子節點)放在不一樣的頁面中,一個頁面存放一個節點。每一個節點存放數據負載(payload)的空間大小是固定的,若是數據負載超出了節點的大小,那麼超出的部分會存放到附加頁面(overflow page)中,中間節點和葉子節點均可以有附加頁面存放超出的數據負載。頁面的結構以下:
頁面的header結構定義以下:
每一個頁面都被劃分爲若干個數據單元(cell),每一個數據單元(cell)保存一份數據負載(或數據負載的一部分)。數據單元的header定義以下:
數據單元(cell)是大小可變的字節數組,一個數據單元(cell)保存一份數據負載(payload)。
每一個頁面能夠存儲的數據單元數量不能少於最低單元數量,也不能多餘最大單元數量,它們分別由數據庫文件頭中的"Maximum embedded payload fraction"和"Minimum embedded payload fraction"決定,數據負載超出的部分會被放到附加頁面中,多個附加頁面會經過鏈表組織起來。
總結,SQLite會把數據庫中的表和索引經過B/B+-tree組織起來,B/B+tree會在插入或刪除節點時作自動調整以保持平衡,而且會自動回收和重用空閒頁面。B/B+-tree提供的查詢、插入和刪除操做的時間複雜度是O(logn),遍歷記錄的時間複雜度是O(1)。
SQLite後端的頂層組件是虛擬機,它是前端和後端的接口,虛擬機在本地系統的的上層又抽象出了一個虛擬的機器。虛擬機執行的是SQLite內部定義的字節碼(bytecode)編程語言,這個編程語言是爲執行數據庫搜索、讀取和修改特別設計的。虛擬機接收和執行字節碼程序,再經過B+-tree具體執行數據庫操做並返回操做結果。
一個字節碼(bytecode)程序是由sqlite3_stmt類型的對象封裝的,由前端的命令解析器在解析SQL命令後生成。
1.《Inside SQLite》