小巧。快速。可靠。
任選三項。
WAL 模式檔案格式

本文件說明 WAL 模式 在 Unix 和 Windows 上的實作方式的低階詳細資料。

獨立的 檔案格式 說明提供 WAL 模式 中使用的資料庫檔案和寫入頭記錄檔結構的詳細資料。但鎖定協定的詳細資料和 WAL 索引的格式刻意省略,因為這些詳細資料留給個別 VFS 實作自行決定。本文件填補 Unix 和 Windows VFS 的這些遺漏詳細資料。

為了完整性,檔案格式 文件和其他地方包含的一些較高階格式化資訊在此複製,當它與 WAL 模式處理相關時。

1. 磁碟上的檔案

在實際使用中,WAL 模式資料庫的狀態由三個獨立的檔案說明

  1. 名稱為「X」的任意主要資料庫檔案。
  2. 寫入前日誌檔案,通常命名為「X-wal」。
  3. wal-index 檔案,通常命名為「X-shm」。

1.1. 主資料庫檔案

主資料庫檔案的格式如 檔案格式 文件中所述。主資料庫中偏移量 18 和 19 處的 檔案格式版本號碼 必須同為 2,以表示資料庫處於 WAL 模式。主資料庫可以有底層檔案系統允許的任意名稱。不需要特殊檔案字尾,不過「.db」、「.sqlite」和「.sqlite3」似乎是熱門選擇。

1.2. 寫入前日誌或「-wal」檔案

寫入前日誌或「wal」檔案是一個向前滾動的日誌,記錄已提交但尚未套用至主資料庫的交易。wal 檔案格式的詳細資料說明在主 檔案格式 文件的 WAL 格式 子區段中。wal 檔案的命名方式是在主資料庫檔案名稱的結尾加上四個字元「-wal」。除了在 8+3 檔案系統中,此類名稱不被允許,在這種情況下,檔案字尾會變更為「.WAL」。但由於 8+3 檔案系統越來越少見,通常可以忽略這個例外情況。

1.3. Wal-Index 或「-shm」檔案

wal-index 檔案或「shm」檔案實際上並不用作檔案。相反地,個別資料庫用戶端會 mmap shm 檔案,並將其用作共享記憶體,以協調對資料庫的存取,並作為快取,以快速找出 wal 檔案中的區塊。shm 檔案的名稱是主資料庫檔案名稱,加上四個字元「-shm」。或者,對於 8+3 檔案系統,shm 檔案是主資料庫檔案,其字尾變更為「.SHM」。

shm 不包含任何資料庫內容,且在崩潰後不需要恢復資料庫。因此,第一個連線到靜止資料庫的用戶端通常會在 shm 檔案存在時將其截斷。由於 shm 檔案的內容不需要在崩潰時保留,因此 shm 檔案永遠不會 fsync()-ed 到磁碟。事實上,如果 SQLite 有機制可以告訴作業系統永遠不要將 shm 檔案保留到磁碟,而是始終將其保留在快取記憶體中,SQLite 會使用該機制來避免與 shm 檔案相關的任何不必要的磁碟 I/O。但是,標準 posix 中不存在這樣的機制。

由於 shm 僅用於協調並發用戶端之間的存取,因此如果設定了獨佔鎖定模式,則會省略 shm 檔案作為最佳化。當設定獨佔鎖定模式時,SQLite 會使用堆疊記憶體來取代記憶體映射的 shm 檔案。

1.4. 檔案生命週期

當 WAL 模式資料庫處於使用中時,上述三個檔案通常都存在。但如果設定了獨佔鎖定模式,則會省略 Wal-Index 檔案。

如果使用資料庫的最後一個用戶端透過呼叫sqlite3_close()正常關閉,則會自動執行檢查點,以將所有資訊從 wal 檔案傳輸到主資料庫,並且 shm 檔案和 wal 檔案都會取消連結。因此,當資料庫未被任何用戶端使用時,通常只有主資料庫檔案存在於磁碟上。但是,如果最後一個用戶端在關閉之前沒有呼叫sqlite3_close(),或者如果最後一個斷開連線的用戶端是唯讀用戶端,則最後的清理作業不會發生,而 shm 和 wal 檔案即使在資料庫未被使用時仍可能存在於磁碟上。

1.5. 變異

PRAGMA locking_mode=EXCLUSIVE(獨佔鎖定模式)設定時,一次只允許一個用戶端開啟資料庫。由於只有一個用戶端可以使用資料庫,因此會省略 shm 檔案。單一用戶端會使用堆疊記憶體中的緩衝區來取代記憶體映射的 shm 檔案。

如果讀取/寫入用戶端在關閉之前呼叫 sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL),則在關閉時仍會執行檢查點,但不會刪除 shm 檔案和 wal 檔案。這允許後續的唯讀用戶端連線並讀取資料庫。

2. WAL 索引檔案格式

WAL 索引或「shm」檔案用於協調多個用戶端對資料庫的存取,並作為快取,以協助用戶端快速在 wal 檔案中找到區塊。

由於 shm 檔案不參與復原,因此 shm 檔案不需要與機器位元組順序無關。因此,shm 檔案中的數值會寫入主機電腦的原生位元組順序,而不是轉換成特定跨平台位元組順序,如同主資料庫檔案和 wal 檔案所做的一樣。

shm 檔案包含一個或多個雜湊表,每個雜湊表大小為 32768 位元組。不過,第一個雜湊表的前面會保留 136 位元組的標頭,因此第一個雜湊表的大小只有 32632 位元組。shm 檔案的總大小永遠是 32768 的倍數。在大部分情況下,shm 檔案的總大小剛好是 32768 位元組。只有當 wal 檔案變得很大的時候 (超過 4079 個畫面),shm 檔案才需要擴充到超過一個雜湊表。由於預設的自動檢查點閾值為 1000,因此 WAL 檔案很少會達到讓 shm 檔案擴充的 4079 閾值。

2.1. WAL-Index 標頭

shm 檔案的前 136 個位元組是標頭。shm 標頭有三個主要區塊,如下所示

WAL-Index 標頭區塊
位元組說明
0..47WAL Index 資訊的第一個副本
48..95WAL Index 資訊的第二個副本
96..135檢查點資訊和鎖定

shm 標頭的個別欄位 (從 WAL 標頭複製的鹽值除外) 都是主機電腦原生位元組順序中的無符號整數。鹽值是從 WAL 標頭複製的精確副本,並採用 WAL 檔案使用的任何位元組順序。整數的大小可能是 8、16、32 或 64 位元。shm 標頭的個別欄位的詳細分類如下

WAL-Index Header Details
BytesNameMeaning
0..3iVersion The WAL-index format version number. Always 3007000.
4..7  Unused padding space. Must be zero.
8..11iChange Unsigned integer counter, incremented with each transaction
12isInit The "isInit" flag. 1 when the shm file has been initialized.
13bigEndCksum True if the WAL file uses big-ending checksums. 0 if the WAL uses little-endian checksums.
14..15szPage The database page size in bytes, or 1 if the page size is 65536.
16..19mxFrame Number of valid and committed frames in the WAL file.
20..23nPage Size of the database file in pages.
24..31aFrameCksum Checksum of the last frame in the WAL file.
32..39aSalt The two salt value copied from the WAL file header. These values are in the byte-order of the WAL file, which might be different from the native byte-order of the machine.
40..47aCksum A checksum over bytes 0 through 39 of this header.
48..95  A copy of bytes 0 through 47 of this header.
96..99nBackfill Number of WAL frames that have already been backfilled into the database by prior checkpoints
100..119read-mark[0..4] Five "read marks". Each read mark is a 32-bit unsigned integer (4 bytes).
120..127  Unused space set aside for 8 file locks.
128..132nBackfillAttempted Number of WAL frames that have attempted to be backfilled but which might not have been backfilled successfully.
132..136  Unused space reserved for further expansion.

2.1.1. mxFrame 欄位

偏移量 16 (並在偏移量 64 重複) 的 32 位元無符號整數是 WAL 中有效畫面的數量。由於 WAL 畫面從 1 開始編號,因此 mxFrame 也是 WAL 中最後一個有效提交畫面的索引。提交畫面是一個在畫面標頭的第 4 到第 7 個位元組中具有非零「資料庫大小」值的畫面,並表示交易結束。

當 mxFrame 欄位為零時,表示 WAL 為空,且所有內容應直接從資料庫檔案取得。

當 mxFrame 等於 nBackfill 時,表示 WAL 中的所有內容已寫回資料庫。在這種情況下,所有內容都可以直接從資料庫讀取。此外,如果沒有其他連線持有 WAL_READ_LOCK(N) 的鎖定,其中 N>0,則下一個寫入器可以自由地 重設 WAL

mxFrame 值永遠大於或等於 nBackfill 和 nBackfillAttempted。

2.1.2. nBackfill 欄位

WAL 索引標頭中偏移量 128 處的 32 位元無符號整數稱為「nBackfill」。此欄位儲存已複製回主資料庫的 WAL 檔案中框架的數量。

nBackfill 數字永遠不會大於 mxFrame。當 nBackfill 等於 mxFrame 時,表示 WAL 內容已完全寫回資料庫,如果沒有任何 WAL_READ_LOCK(N) 的鎖定,其中 N>0,則可以 重設 WAL

只有在持有 WAL_CKPT_LOCK 時才能增加 nBackfill。然而,nBackfill 在 WAL 重設 期間會變為零,而這會在持有 WAL_WRITE_LOCK 時發生。

2.1.3. WAL 鎖定

標頭中保留八個位元組的空間,以使用 sqlite3_io_methods 物件中的 xShmLock() 方法支援檔案鎖定。由於某些 VFS(例如:Windows)可能會使用強制檔案鎖定來實作鎖定,因此 SQLite 永遠不會讀取或寫入這八個位元組。

以下是支援的八個鎖定

受 xShmLock() 控制的 WAL-Index 鎖
名稱偏移
xShmLock檔案
WAL_WRITE_LOCK 0 120
WAL_CKPT_LOCK 1 121
WAL_RECOVER_LOCK 2 122
WAL_READ_LOCK(0) 3 123
WAL_READ_LOCK(1) 4 124
WAL_READ_LOCK(2) 5 125
WAL_READ_LOCK(3) 6 126
WAL_READ_LOCK(4) 7 127

待完成:更多關於標頭的資訊

2.2. WAL-Index Hash 表

shm 檔案中的 hash 表設計用於快速回答下列問題

FindFrame(P,M):給定頁碼 P 和最大 WAL 框架索引 M,傳回頁面 P 中不超過 M 的最大 WAL 框架索引,或如果沒有不超過 M 的頁面 P 框架,則傳回 NULL。

假設資料類型「u8」、「u16」和「u32」分別表示長度為 8、16 和 32 位元的無符號整數。那麼,shm 檔案的前 32768 位元組單元組織如下

u8 aWalIndexHeader[136];
u32 aPgno[4062];
u16 aHash[8192];

shm 檔案的第二個和所有後續的 32768 位元組單元如下

u32 aPgno[4096];
u16 aHash[8192];

aPgno 項目共同記錄儲存在 WAL 檔案所有框架中的資料庫頁碼。第一個 hash 表上的 aPgno[0] 項目記錄儲存在 WAL 檔案第一個框架中的資料庫頁碼。第一個 hash 表中的 aPgno[i] 項目是 WAL 檔案中第 i 個框架的資料庫頁碼。第二個 hash 表的 aPgno[k] 項目是 WAL 檔案中第 (k+4062) 個框架的資料庫頁碼。shm 檔案中第 n 個 32768 位元組 hash 表的 aPgno[k] 項目(對於 n>1)儲存儲存在 WAL 檔案第 (k+4062+4096*(n-2)) 個框架中的資料庫頁碼。

以下為描述 aPgno 值的另一種方式:如果您將所有 aPgno 值視為一個連續陣列,則儲存在 WAL 檔案第 i 個畫面的資料庫頁面號碼會儲存在 aPgno[i] 中。當然,aPgno 並非連續陣列。前 4062 個項目位於 shm 檔案的前 32768 位元組單位,後續值則以 4096 個項目的區塊儲存在 shm 檔案的後續單位中。

計算 FindFrame(P,M) 的一種方式是從第 M 個項目開始掃描 aPgno 陣列,並朝著開頭往回運作,並回傳 aPgno[J]==P 的 J。此演算法會運作,而且會比搜尋整個 WAL 檔案尋找具有頁面號碼 P 的最新畫面還要快。不過,透過使用 aHash 結構,可以讓搜尋速度更快。

資料庫頁面號碼 P 會使用下列雜湊函數對應到雜湊值

h = (P * 383)%8192

此函數會將每個頁面號碼對應到 0 至 8191(含)之間的整數。每個 32768 位元組 shm 檔案單位的 aHash 欄位會將 P 值對應到同一個單位的 aPgno 欄位的索引,如下所示

  1. 計算雜湊值:h = P * 383
  2. 讓 X 成為最大的連續整數集合 {h, h+1, h+2, ..., h+N},使得對於 X 中的每個 j,aPgno[j%8192]!=0。如果 aPgno[h%8192]==0,X 集合會是空的。X 集合可以透過從 h%8192 值開始,並將 h%8192 加入 X 並遞增 h,直到遇到第一個為零的 aPgno[h%8192] 項目,輕鬆計算出來。
  3. X 集合包含 shm 檔案目前 32768 位元組單位的 aPgno 中每個項目的索引,而這些項目可能是 FindFrame(P,M) 函數的解。必須個別檢查這些項目,以確保 aPgno 值為 P,且畫面號碼不超過 M。通過這兩個測試的最大畫面號碼就是答案。

aPgno 陣列中的每個項目在 aHash 陣列中都有相應的單一項目。aHash 中的可用槽位比 aPgno 中的可用槽位多。aHash 中未使用的槽位會填入零。由於 aHash 中保證會有未使用的槽位,這表示計算 X 的迴圈保證會終止。X 的預期大小小於 2。最糟的情況是 X 會與 aPgno 中的項目數相同,在這種情況下,演算法執行的速度與 aPgno 的線性掃描大約相同。但這種最糟情況的效能極為罕見。通常,X 的大小會很小,而使用 aHash 陣列可以讓人以更快的速度計算 FindFrame(P,M)。

以下是描述雜湊查詢演算法的另一種方式:從 h = (P * 383)%8192 開始,查看 aHash[h] 和後續項目,在 h 達到 8192 時換行至零,直到找到 aHash[h]==0 的項目。所有頁面號碼為 P 的 aPgno 項目都會有一個索引,而這個索引是如此計算而來的 aHash[h] 值之一。但並非所有計算出來的 aHash[h] 值都會符合比對條件,因此你必須獨立檢查它們。速度優勢來自於通常這組 h 值非常小。

請注意,shm 檔案的每個 32768 位元組單位都有自己的 aHash 和 aPgno 陣列。單一單位的 aHash 陣列僅有助於在同一個單位中找到 aPgno 項目。整體的 FindFrame(P,M) 函式需要從最新的單位開始進行雜湊查詢,並向後運作至最舊的單位,直到找到答案。

2.3. 鎖定矩陣

在 WAL 模式中,存取會透過 sqlite3_io_methods 物件的 xLock 和 xUnlock 方法控制的傳統 DELETE 模式鎖定,以及透過 sqlite3_io_methods 物件的 xShmLock 方法控制的 WAL 鎖定來協調。

在概念上,只有一個 DELETE 模式鎖定。單一資料庫連線的 DELETE 模式鎖定可以精確地處於下列狀態之一

  1. SQLITE_LOCK_NONE(未鎖定)
  2. SQLITE_LOCK_SHARED(讀取)
  3. SQLITE_LOCK_RESERVED(讀取,等待寫入)
  4. SQLITE_LOCK_PENDING(新的讀取器已封鎖,等待寫入)
  5. SQLITE_LOCK_EXCLUSIVE(寫入)

DELETE 模式鎖定會儲存在主資料庫檔案的 鎖定位元組頁面 上。只有 SQLITE_LOCK_SHARED 和 SQLITE_LOCK_EXCLUSIVE 是 WAL 模式資料庫的因素。其他鎖定狀態會用於回滾模式,但不會用於 WAL 模式。

上述已說明 WAL 模式鎖定

2.3.1. 各種鎖定的使用方式

下列規則顯示如何使用每個鎖定。

2.3.2. 需要鎖定以及這些操作使用哪些鎖定的操作

3. 復原

復原是重建 WAL 索引的程序,以便與 WAL 同步。

復原是由連線到 WAL 模式資料庫的第一個執行緒執行。復原會還原 WAL 索引,以便準確描述 WAL 檔案。如果在第一個執行緒連線到資料庫時沒有 WAL 檔案,則沒有任何需要復原的內容,但復原程序仍會執行以初始化 WAL 索引。

如果 WAL 索引實作為記憶體映射檔,且該檔對第一個連線執行緒為唯讀,則該執行緒會建立一個私有堆疊記憶體 ersazt WAL 索引,並執行復原常式來填入該私有 WAL 索引。資料結果相同,但會以私有方式保留,而不是寫入到公用共用記憶體區域。

復原會執行一次 WAL 通過,從頭到尾。在讀取 WAL 的每個畫格時,會驗證檢查碼。掃描會在檔案結尾或第一個無效檢查碼處停止。mxFrame 欄位會設定為 WAL 中最後一個有效提交畫格的索引。由於 WAL 畫格編號從 1 開始索引,因此 mxFrame 也是 WAL 中有效畫格的數量。「提交畫格」是指在畫格標頭的第 4 到第 7 個位元組中具有非零值的畫格。由於復原程序無法得知先前可能已複製回資料庫的 WAL 畫格數量,因此會將 nBackfill 值初始化為零。

在復原全域共用記憶體 WAL 索引期間,會對 WAL_WRITE_LOCK、WAL_CKPT_LOCK、WAL_RECOVER_LOCK 以及 WAL_READ_LOCK(1) 到 WAL_READ_LOCK(4) 保持獨佔鎖定。換句話說,會獨佔鎖定與 WAL 索引相關的所有鎖定,除了 WAL_READ_LOCK(0)。這會防止任何其他執行緒寫入資料庫,以及讀取 WAL 中保留的任何交易,直到復原完成為止。

此頁面最後修改於 2022-01-08 05:02:57 UTC