[TOC]
## 什么是 WAL
WAL 的全稱是 Write Ahead Logging(預寫日志),它是很多數據庫中用于實現原子事務的一種機制。SQLite 在 3.7.0 版本引入該特性,在此之前 SQLite 實現原子提交和回滾的方法是 rollback journal(回滾日志)。
## WAL 工作原理
* Rollback Journal
SQLite 在引入 WAL 模式之前,使用的是 Rollback Journal 機制實現原子事務。Rollback Journal 的原理是,在修改數據庫文件中數據之前,先將修改所在分頁中的數據備份在另外一個地方,然后才將修改寫入到數據庫文件中;如果事務失敗,則將備份數據拷貝回來,撤銷修改;如果事務成功,則刪除備份數據提交修改。
* WAL
WAL 的方法正好反過來了,原始內容保留在數據庫文件中,修改數據而是寫入到另外一個稱為 WAL 的文件中,如果事務失敗,WAL 中的記錄會被忽略,撤銷修改;如果事務成功,它將隨后的某個時間被寫回到數據庫文件中,提交修改。
同步 WAL 文件和數據庫文件的行為被稱為 CheckPoint(檢查點)。
在讀數據庫時,SQLite 將在 WAL 文件中搜索,找到最后一個寫入點,記住它,并忽略在此之后的寫入數據(這保證了讀和寫可以并發執行);隨后它確定所要讀的數據所在頁是否在 WAL 文件中,如果在,則讀 WAL 文件中的數據,如果不在,則直接讀數據庫文件中的數據。
在寫入數據庫時,SQLite 將數據寫入到 WAL 文件中即可,但是必須保證獨占寫入,因此寫和寫之間不能并發執行。WAL 在實現的過程中,使用了共享內存技術,因此所有的讀寫進程必須在同一個機器上,否則無法保證數據的一致性。
## 檢查點
當然,最終希望將 WAL 文件中附加的所有事務轉移回原始數據庫。將 WAL 文件事務移回數據庫稱為“檢查點”。
考慮回滾日志和預寫日志之間差異的另一種方法是,在回滾日志方法中,存在兩種原始操作,即讀取和寫入,而對于預寫日志,此時會有三種原始操作:讀取、寫入和檢查點。
默認情況下,當 WAL 文件達到 1000 頁的閾值大小時,SQLite 自動執行一個檢查點。使用 WAL 的應用程序不需要執行任何附加操作即可享受到 CheckPoint。但是如果需要應用程序調整自動檢查點閾值,可以使用 [SQLITE\_DEAULT\_WAL\_AUTOCHECKPOINT](https://links.jianshu.com/go?to=https%3A%2F%2Fwww.sqlite.org%2Fcompile.html%23default_wal_autocheckpoint) 編譯時選項指定其它默認值。
## 并發
在 WAL 模式數據庫上開始讀數據庫操作時,它首先會記住 WAL 中最后一個有效提交記錄的位置。將此點稱為“結束標記”。由于 WAL 可以在各種讀取器連接到數據庫的同時不斷增長并添加新的提交記錄,因此每個讀取器都可能具有自己的結束標記。但是對于任何特定的讀取,結束標記在事務期間不會改變,從而確保了單個讀取事務只能看到數據庫內容,因為它存在于單個時間點。
當讀取需要一頁內容時,它首先檢查 WAL 以查看讀取頁面是否出現在該頁面中,如果是,它將拉入 WAL 中出現在讀者終點標記之前的頁面的最后副本。如果在 WAL 中沒有頁面的副本位于讀者的結尾標記之前,則從原始數據庫文件中讀取頁面。讀取可以存在于單獨的進程中,因此避免強迫每個讀取掃描整個 WAL 尋找頁面(WAL 文件可以增長到數兆字節,具體取決于運行檢查點的頻率),這種數據結構成為“wal-index”保留在共享內存中,這有助于讀取以最少的 I/O 速度在 WAL 中定位頁面。wal-index 大大提高了讀取的性能,但是共享內存的使用意味著所有讀取者必須在同一臺計算機上。
寫入者僅將新內容附加到 WAL 文件的末尾。因為寫入不做任何會干擾讀取數據行為的事情,所以寫和讀可以同時運行。但是,由于只有一個 WAL 文件,所以一次只能有一個寫入器。
檢查點操作從 WAL 文件中獲取內容,并將其回傳到原始數據文件中。檢查點可以與讀取任務同時運行,但是當檢查點到達 WAL 中的頁面,超過任何當前讀取任務的結束標記時,必須停止。檢查點必須在該點處停止,因為它可能會覆蓋讀取者正在使用的部分數據庫文件。該檢查點會記住(wal-index中)到達的距離,并繼續將內容從 WAL 傳輸到下一次調用中斷位置的數據庫。
因此,長時間運行的讀取事務可能會阻止檢查指針運行。但是大概每個讀取事務最終都會結束,并且檢查指針將能夠繼續。
每當發生寫操作時,寫入者都會檢查指針取得了多少進展,如果整個 WAL 已傳輸到數據庫并進行了同步,并且如果沒有讀取正在使用 WAL,則寫入者將 WAL 重新從 0 開始,并在 WAL 開始時進行新交易。這種機制可以防止 WAL 文件無限制地增長。
## 性能問題
寫事務非常快,絕大多數情況下,WAL 會提高 SQLite 的事務性能,因為它們只涉及一次寫入內容(相對回滾日志事務而言則是兩次),而且寫入都是順序的。但是在某些極端情況下,卻會導致 SQLite 事務的性能下降。
但是隨著 WAL 文件大小的增加,讀取性能會下降,因為每個讀取任務都必須檢查 WAL 文件中的內容,并且檢查 WAL 文件所需要的時間與 WAL 文件的大小成正比。wal-index 有助于更快地在 WAL 文件中查找內容,但是性能隨著 WAL 文件大小的增加而下降。因此為了保持良好的讀取性能,要通過定期運行檢查點來減小 WAL 文件的大小。
檢查點確實需要同步操作,以避免斷點或硬重啟后數據庫損壞的可能性。在將內容從 WAL 移到數據庫之前,必須將 WAL 同步到持久性存儲,并且在重置 WAL 之前必須同步數據庫文件。CheckPoint 還需要更多尋求。檢查指針會盡最大努力對數據庫進行順序的頁面寫入,但是即使這樣,頁面寫入之間通常也會散布著許多查找操作。這些因素共同導致檢查點比寫入事務慢。
默認策略是允許連續的寫事務增長 WAL,直到 WAL 變為大約 1000 頁,然后為每個后續 COMMIT 運行檢查點操作,直到 WAL 重置為小于 1000 頁為止。默認情況下,檢查點將由執行 COMMIT 的同一線程自動運行,該線程是 WAL 超過其大小限制。這會導致大多數 COMMIT 操作非常快,但偶爾的 COMMIT(觸發檢查點的操作)會慢的多。如果不希望這種影響,則應用程序可以禁用自動檢查點,并在單獨的線程或單獨的進程中運行定期檢查點。
## WAL 的優點與缺點
優點:
1. 讀操作不會阻塞寫操作,同時寫操作也不會阻塞讀操作。這是并發管理的“黃金準則”。
2. 在大多數操作場景中,與回滾日志相比,WAL 相當快。
3. 磁盤 I/O 變得更可預見,更少的 fsync 系統調用,因為所有的 WAL 寫操作是線性寫入日志文件,很多 I/O 變的連續并能夠按計劃執行。
缺點:
1. 所有的處理被綁定到單個主機上。也就是說,不能再如 NFS 這樣的網絡文件系統上使用 WAL。
2. 為滿足 WAL 和相關共享內存的需要,使用 WAL 引入了里兩個額外的半持久性文件-wal 和-shm。對于那些使用SQLite 數據庫作為應用程序文件格式是不具有吸引力的。這也影響了只讀環境,因為-shm文件必須是可寫的,并且/或數據庫所在目錄也必須是可寫的。
3. 對于非常大的事務,WAL 的性能將會降低。雖然 WAL 是一個高性能選項,但是非常大或運行時間非常長的事務會引入額外的開銷。
# 參考資料
[SQLite 數據庫 WAL 工作模式原理簡介](https://www.jianshu.com/p/b47986e7e734)
- 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 性能優化
- 數據跨平臺