[TOC]
## 架構
SQLite采用了模塊的設計,它由三個子系統,包括8個獨立的模塊構成。

### 接口(Interface)
接口由SQLite C API組成,也就是說不管是程序、腳本語言還是庫文件,最終都是通過它與SQLite交互的(我們通常用得較多的ODBC/JDBC最后也會轉化為相應C API的調用)。
### 編譯器(Compiler)
在編譯器中,分詞器(Tokenizer)和分析器(Parser)對SQL進行語法檢查,然后把它轉化為底層能更方便處理的分層的數據結構---語法樹,然后把語法樹傳給代碼生成器(code generator)進行處理。而代碼生成器根據它生成一種針對SQLite的匯編代碼,最后由虛擬機(Virtual Machine)執行。
### 虛擬機(Virtual Machine)
架構中最核心的部分是虛擬機,或者叫做虛擬數據庫引擎(Virtual Database Engine,VDBE)。它和Java虛擬機相似,解釋執行字節代碼。VDBE的字節代碼由128個操作碼(opcodes)構成,它們主要集中在數據庫操作。它的每一條指令都用來完成特定的數據庫操作(比如打開一個表的游標)或者為這些操作棧空間的準備(比如壓入參數)。總之,所有的這些指令都是為了滿足SQL命令的要求(關于VM,后面會做詳細介紹)。
### 后端(Back-End)
后端由B-樹(B-tree),頁緩存(page cache,pager)和操作系統接口(即系統調用)構成。B-tree和page cache共同對數據進行管理。B-tree的主要功能就是索引,它維護著各個頁面之間的復雜的關系,便于快速找到所需數據。而pager的主要作用就是通過OS接口在B-tree和Disk之間傳遞頁面。
SQLite由很多部分組成-parser,tokenize,virtual machine等等。但是從程序員的角度,最需要知道的是:connection, statements, B-tree和pager
## API層
由兩部分組成: 核心API(core API) 和擴展API(extension API)
核心API的函數實現基本的數據庫操作:連接數據庫,處理SQL,遍歷結果集。它也包括一些實用函數,比如字符串轉換,操作控制,調試和錯誤處理。
擴展API通過創建你自定義的SQL函數去擴展SQLite。

### Connection
一個連接(Connection)代表在一個獨立的事務環境下的一個連接A
### Statements
每一個statement都和一個connection關聯,它通常表示一個編譯過的SQL語句,在內部,它以VDBE字節碼表示。Statement包括執行一個命令所需要一切,包括保存VDBE程序執行狀態所需的資源,指向硬盤記錄的B-樹游標,以及參數等等。
### Transaction
一個連接(connection)可以包含多個(statement),而且每個連接有一個與數據庫關聯的B-tree和一個pager。Pager在連接中起著很重要的作用,因為它管理事務、鎖、內存緩存以及負責崩潰恢復(crash recovery)。當你進行數據庫寫操作時,記住最重要的一件事:在任何時候,只在一個事務下執行一個連接。
一個事務的生命和statement差不多,你也可以手動結束它。默認情況下,事務自動提交,當然你也可以通過BEGIN..COMMIT手動提交。接下來就是鎖的問題。
### 鎖的狀態

關于這個圖有以下幾點值得注意:
一個事務可以在UNLOCKED,RESERVED或EXCLUSIVE三種狀態下開始。默認情況下在UNLOCKED時開始。
白色框中的UNLOCKED, PENDING, SHARED和 RESERVED可以在一個數據庫的同一時存在。
從灰色的PENDING開始,事情就變得嚴格起來,意味著事務想得到排斥鎖(EXCLUSIVE)(注意與白色框中的區別)。
//1. 【讀】會獲取到SHARED鎖 ;【寫page】會獲取到RESERVED鎖
//2. SQLite可以高效的處理在同一時刻的多個讀連接和一個寫連接。
//3. page要寫入數據庫文件時,會先去獲取EXCLUSIVE鎖(排斥鎖)
雖然鎖有這么多狀態,但是從體質上來說,只有兩種情況:讀事務和寫事務。
#### 讀事務
~~~
db = open('foods.db')
db.exec('BEGIN')
db.exec('SELECT * FROM episodes')
db.exec('SELECT * FROM episodes')
db.exec('COMMIT')
db.close()
~~~
由于顯式的使用了BEGIN和COMMIT,兩個SELECT命令在一個事務下執行。第一個exec()執行時,connection處于SHARED,然后第二個exec()執行,當事務提交時,connection又從SHARED回到UNLOCKED狀態,如下:
UNLOCKED→PENDING→SHARED→UNLOCKED
如果沒有BEGIN和COMMIT兩行時如下:
UNLOCKED→PENDING→SHARED→UNLOCKED→PENDING→ SHARED→UNLOCKED
## 后端

### B-tree
B-Tree使得VDBE可以在**O(logN)**下查詢,插入和刪除數據,以及O(1)下雙向遍歷結果集。B-Tree不會直接讀寫磁盤,它僅僅維護著頁面(pages)之間的關系。當B-TREE需要頁面或者修改頁面時,它就會調用Pager。當修改頁面時,pager保證原始頁面首先寫入日志文件,當它完成寫操作時,pager根據事務狀態決定如何做。B-tree不直接讀寫文件,而是通過page cache這個緩沖模塊讀寫文件對于性能是有重要意義的。
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太大不能存在一個頁面內進便保存到多個頁面。
### Page Cache事務處理
pager層是SQLite實現最為核心的模塊,它具有四大功能:I/O,頁面緩存,并發控制和日志恢復。而這些功能不僅是上層Btree的基礎,而且對系統的性能和健壯性有關至關重要的影響。其中并發控制和日志恢復是事務處理實現的基礎。SQLite并發控制的機制非常簡單——封鎖機制;別外,它的查詢優化機制也非常簡單——基于索引。
#### 初始狀態
當一個數據庫連接第一次打開時,狀態如圖所示。圖中最右邊(“Disk”標注)表示保存在存儲設備中的內容。每個方框代表一個扇區。藍色的塊表示這個扇區保存了原始數據。圖中中間區域是操作系統的磁盤緩沖區。開始的時候,這些緩存是還沒有被使用,因此這些方框是空白的。圖中左邊區域顯示SQLite用戶進程的內存。因為這個數據庫連接剛剛打開,所以還沒有任何數據記錄被讀入,所以這些內存也是空的。

#### 獲取讀鎖
在SQLite寫數據庫之前,它必須先從數據庫中讀取相關信息。比如,在插入新的數據時,SQLite會先從sqlite\_master表中讀取數據庫模式(相當于數據字典),以便編譯器對INSERT語句進行分析,確定數據插入的位置。
在進行讀操作之前,必須先獲取數據庫的共享鎖(shared lock),共享鎖允許兩個或更多的連接在同一時刻讀取數據庫。但是共享鎖不允許其它連接對數據庫進行寫操作
shared lock存在于操作系統磁盤緩存,而不是磁盤本身。文件鎖的本質只是操作系統的內核數據結構,當操作系統崩潰或掉電時,這些內核數據也會隨之消失。

#### 讀取數據
一旦得到shared lock,就可以進行讀操作。如圖所示,數據先由OS從磁盤讀取到OS緩存,然后再由OS移到用戶進程空間。一般來說,數據庫文件分為很多頁,而一次讀操作只讀取一小部分頁面。如圖,從8個頁面讀取3個頁面

#### 獲取Reserved Lock
在對數據進行修改操作之前,先要獲取數據庫文件的Reserved Lock,Reserved Lock和shared lock的相似之處在于,它們都允許其它進程對數據庫文件進行讀操作。Reserved Lock和Shared Lock可以共存,但是只能是一個Reserved Lock和多個Shared Lock——多個Reserved Lock不能共存。所以,在同一時刻,只能進行一個寫操作。
Reserved Lock意味著當前進程(連接)想修改數據庫文件,但是還沒開始修改操作,所以其它的進程可以讀數據庫,但不能寫數據庫

#### 創建恢復日志(Creating A Rollback Journal File)
在對數據庫進行寫操作之前,SQLite先要創建一個單獨的日志文件,然后把要修改的頁面的原始數據寫入日志。回滾日志包含一個日志頭(圖中的綠色)——記錄數據庫文件的原始大小。所以即使數據庫文件大小改變了,我們仍知道數據庫的原始大小。
從OS的角度來看,當一個文件創建時,大多數OS(Windows,Linux,Mac OS X)不會向磁盤寫入數據,新創建的文件此時位于磁盤緩存中,之后才會真正寫入磁盤。如圖,日志文件位于OS磁盤緩存中,而不是位于磁盤。

#### 修改位于用戶進程空間的頁面(Changing Database Pages In User Space)
頁面的原始數據寫入日志之后,就可以修改頁面了——位于用戶進程空間。每個數據庫連接都有自己私有的空間,所以頁面的變化只對該連接可見,而對其它連接的數據仍然是磁盤緩存中的數據。從這里可以明白一件事:一個進程在修改頁面數據的同時,其它進程可以繼續進行讀操作。圖中的紅色表示修改的頁面。

#### 日志文件刷入磁盤(Flushing The Rollback Journal File To Mass Storage)
接下來把日志文件的內容刷入磁盤,這對于數據庫從意外中恢復來說是至關重要的一步。而且這通常也是一個耗時的操作,因為磁盤I/O速度很慢。
這個步驟不只把日志文件刷入磁盤那么簡單,它的實現實際上分成兩步:首先把日志文件的內容刷入磁盤(即頁面數據);然后把日志文件中頁面的數目寫入日志文件頭,再把header刷入磁盤(這一過程在代碼中清晰可見)。

#### 獲取排斥鎖(Obtaining An Exclusive Lock)
在對數據庫文件進行修改之前(注:這里不是內存中的頁面),我們必須得到數據庫文件的排斥鎖(Exclusive Lock)。得到排斥鎖的過程可分為兩步:首先得到Pending lock;然后Pending lock升級到exclusive lock。
Pending lock允許其它已經存在的Shared lock繼續讀數據庫文件,但是不允許產生新的shared lock,這樣做目的是為了防止寫操作發生餓死情況。一旦所有的shared lock完成操作,則pending lock升級到exclusive lock。

#### 修改的頁面寫入文件(Writing Changes To The Database File)
一旦得到exclusive lock,其它的進程就不能進行讀操作,此時就可以把修改的頁面寫回數據庫文件,但是通常OS都把結果暫時保存到磁盤緩存中,直到某個時刻才會真正把結果寫入磁盤。

#### 修改結果刷入存儲設備(Flushing Changes To Mass Storage)
為了保證修改結果真正寫入磁盤,這一步必不要少。對于數據庫存的完整性,這一步也是關鍵的一步。由于要進行實際的I/O操作,所以和第7步一樣,將花費較多的時間

#### 刪除日志文件(Deleting The Rollback Journal)
一旦更改寫入設備,日志文件將會被刪除,這是事務真正提交的時刻。如果在這之前系統發生崩潰,就會進行恢復處理,使得數據庫和沒發生改變一樣;如果在這之后系統發生崩潰,表明所有的更改都已經寫入磁盤。SQLite就是根據日志存在情況決定是否對數據庫進行恢復處理。
刪除文件本質上不是一個原子操作,但是從用戶進程的角度來看是一個原子操作,所以一個事務看起來是一個原子操作。
在許多系統中,刪除文件也是一個高代價的操作。作為優化,SQLite可以配置成把日志文件的長度截為0或者把日志文件頭清零。

#### 釋放鎖(Releasing The Lock)
作為原子提交的最后一步,釋放排斥鎖使得其它進程可以開始訪問數據庫了。

## 參考資料
[深入理解sqlite](http://www.hmoore.net/kangdandan/sqlite/64352)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺