本文件說明 WAL 模式 在 Unix 和 Windows 上的實作方式的低階詳細資料。
獨立的 檔案格式 說明提供 WAL 模式 中使用的資料庫檔案和寫入頭記錄檔結構的詳細資料。但鎖定協定的詳細資料和 WAL 索引的格式刻意省略,因為這些詳細資料留給個別 VFS 實作自行決定。本文件填補 Unix 和 Windows VFS 的這些遺漏詳細資料。
為了完整性,檔案格式 文件和其他地方包含的一些較高階格式化資訊在此複製,當它與 WAL 模式處理相關時。
在實際使用中,WAL 模式資料庫的狀態由三個獨立的檔案說明
主資料庫檔案的格式如 檔案格式 文件中所述。主資料庫中偏移量 18 和 19 處的 檔案格式版本號碼 必須同為 2,以表示資料庫處於 WAL 模式。主資料庫可以有底層檔案系統允許的任意名稱。不需要特殊檔案字尾,不過「.db」、「.sqlite」和「.sqlite3」似乎是熱門選擇。
寫入前日誌或「wal」檔案是一個向前滾動的日誌,記錄已提交但尚未套用至主資料庫的交易。wal 檔案格式的詳細資料說明在主 檔案格式 文件的 WAL 格式 子區段中。wal 檔案的命名方式是在主資料庫檔案名稱的結尾加上四個字元「-wal」。除了在 8+3 檔案系統中,此類名稱不被允許,在這種情況下,檔案字尾會變更為「.WAL」。但由於 8+3 檔案系統越來越少見,通常可以忽略這個例外情況。
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 檔案。
當 WAL 模式資料庫處於使用中時,上述三個檔案通常都存在。但如果設定了獨佔鎖定模式,則會省略 Wal-Index 檔案。
如果使用資料庫的最後一個用戶端透過呼叫sqlite3_close()正常關閉,則會自動執行檢查點,以將所有資訊從 wal 檔案傳輸到主資料庫,並且 shm 檔案和 wal 檔案都會取消連結。因此,當資料庫未被任何用戶端使用時,通常只有主資料庫檔案存在於磁碟上。但是,如果最後一個用戶端在關閉之前沒有呼叫sqlite3_close(),或者如果最後一個斷開連線的用戶端是唯讀用戶端,則最後的清理作業不會發生,而 shm 和 wal 檔案即使在資料庫未被使用時仍可能存在於磁碟上。
當 PRAGMA locking_mode=EXCLUSIVE(獨佔鎖定模式)設定時,一次只允許一個用戶端開啟資料庫。由於只有一個用戶端可以使用資料庫,因此會省略 shm 檔案。單一用戶端會使用堆疊記憶體中的緩衝區來取代記憶體映射的 shm 檔案。
如果讀取/寫入用戶端在關閉之前呼叫 sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL),則在關閉時仍會執行檢查點,但不會刪除 shm 檔案和 wal 檔案。這允許後續的唯讀用戶端連線並讀取資料庫。
WAL 索引或「shm」檔案用於協調多個用戶端對資料庫的存取,並作為快取,以協助用戶端快速在 wal 檔案中找到區塊。
由於 shm 檔案不參與復原,因此 shm 檔案不需要與機器位元組順序無關。因此,shm 檔案中的數值會寫入主機電腦的原生位元組順序,而不是轉換成特定跨平台位元組順序,如同主資料庫檔案和 wal 檔案所做的一樣。
shm 檔案包含一個或多個雜湊表,每個雜湊表大小為 32768 位元組。不過,第一個雜湊表的前面會保留 136 位元組的標頭,因此第一個雜湊表的大小只有 32632 位元組。shm 檔案的總大小永遠是 32768 的倍數。在大部分情況下,shm 檔案的總大小剛好是 32768 位元組。只有當 wal 檔案變得很大的時候 (超過 4079 個畫面),shm 檔案才需要擴充到超過一個雜湊表。由於預設的自動檢查點閾值為 1000,因此 WAL 檔案很少會達到讓 shm 檔案擴充的 4079 閾值。
shm 檔案的前 136 個位元組是標頭。shm 標頭有三個主要區塊,如下所示
位元組 | 說明 |
---|---|
0..47 | WAL Index 資訊的第一個副本 |
48..95 | WAL Index 資訊的第二個副本 |
96..135 | 檢查點資訊和鎖定 |
shm 標頭的個別欄位 (從 WAL 標頭複製的鹽值除外) 都是主機電腦原生位元組順序中的無符號整數。鹽值是從 WAL 標頭複製的精確副本,並採用 WAL 檔案使用的任何位元組順序。整數的大小可能是 8、16、32 或 64 位元。shm 標頭的個別欄位的詳細分類如下
Bytes | Name | Meaning |
---|---|---|
0..3 | iVersion | The WAL-index format version number. Always 3007000. |
4..7 | Unused padding space. Must be zero. | |
8..11 | iChange | Unsigned integer counter, incremented with each transaction |
12 | isInit | The "isInit" flag. 1 when the shm file has been initialized. |
13 | bigEndCksum | True if the WAL file uses big-ending checksums. 0 if the WAL uses little-endian checksums. |
14..15 | szPage | The database page size in bytes, or 1 if the page size is 65536. |
16..19 | mxFrame | Number of valid and committed frames in the WAL file. |
20..23 | nPage | Size of the database file in pages. |
24..31 | aFrameCksum | Checksum of the last frame in the WAL file. |
32..39 | aSalt | 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..47 | aCksum | A checksum over bytes 0 through 39 of this header. |
48..95 | A copy of bytes 0 through 47 of this header. | |
96..99 | nBackfill | Number of WAL frames that have already been backfilled into the database by prior checkpoints |
100..119 | read-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..132 | nBackfillAttempted | 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. |
偏移量 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。
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 時發生。
標頭中保留八個位元組的空間,以使用 sqlite3_io_methods 物件中的 xShmLock() 方法支援檔案鎖定。由於某些 VFS(例如:Windows)可能會使用強制檔案鎖定來實作鎖定,因此 SQLite 永遠不會讀取或寫入這八個位元組。
以下是支援的八個鎖定
名稱 | 偏移 | |
---|---|---|
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 |
待完成:更多關於標頭的資訊
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 欄位的索引,如下所示
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) 函式需要從最新的單位開始進行雜湊查詢,並向後運作至最舊的單位,直到找到答案。
在 WAL 模式中,存取會透過 sqlite3_io_methods 物件的 xLock 和 xUnlock 方法控制的傳統 DELETE 模式鎖定,以及透過 sqlite3_io_methods 物件的 xShmLock 方法控制的 WAL 鎖定來協調。
在概念上,只有一個 DELETE 模式鎖定。單一資料庫連線的 DELETE 模式鎖定可以精確地處於下列狀態之一
DELETE 模式鎖定會儲存在主資料庫檔案的 鎖定位元組頁面 上。只有 SQLITE_LOCK_SHARED 和 SQLITE_LOCK_EXCLUSIVE 是 WAL 模式資料庫的因素。其他鎖定狀態會用於回滾模式,但不會用於 WAL 模式。
上述已說明 WAL 模式鎖定。
下列規則顯示如何使用每個鎖定。
SQLITE_LOCK_SHARED
所有連線會在附加到 WAL 模式資料庫時持續持有 SQLITE_LOCK_SHARED。這適用於讀取/寫入連線和唯讀連線。即使不在交易中的連線也會持有 SQLITE_LOCK_SHARED 鎖定。這與回滾模式不同,在回滾模式中,SQLITE_LOCK_SHARED 會在每個交易結束時釋放。
SQLITE_LOCK_EXCLUSIVE
當連線在 WAL 模式和任何各種回滾模式之間變更時,連線會持有獨佔鎖定。連線在從 WAL 模式中斷線時也可能會嘗試取得 EXCLUSIVE 鎖定。如果連線能夠取得 EXCLUSIVE 鎖定,表示它是資料庫中唯一的連線,因此它可能會嘗試檢查點,然後刪除 WAL 索引和 WAL 檔案。
當一個連線在主資料庫上持有共享鎖定時,這將防止任何其他連線取得獨佔鎖定,進而防止 WAL 索引和 WAL 檔案從其他使用者底下刪除,並防止在其他使用者以 WAL 模式存取資料庫時,從 WAL 模式轉換出去。
WAL_WRITE_LOCK
WAL_WRITE_LOCK 僅獨佔鎖定。WAL_WRITE_LOCK 永遠不會取得共享鎖定。
任何將內容追加到 WAL 末端的連線都持有獨佔 WAL_WRITE_LOCK。因此,一次只有一個程序可以將內容追加到 WAL。如果寫入導致WAL 重設發生,則在持有此鎖定的同時,WAL 索引標頭的nBackfill欄位會重設為零。
當連線在共享 WAL 索引上執行復原時,也會持有 WAL_WRITE_LOCK 的獨佔鎖定,以及其他幾個鎖定位元組。
WAL_CKPT_LOCK
WAL_CKPT_LOCK 僅獨佔鎖定。WAL_CKPT_LOCK 永遠不會取得共享鎖定。
任何執行檢查點的連線都持有獨佔 WAL_CKPT_LOCK。在持有此獨佔鎖定的同時,WAL 索引標頭的nBackfill欄位可能會增加,但不能減少。
當連線在共享 WAL 索引上執行復原時,也會持有 WAL_CKPT_LOCK 的獨佔鎖定,以及其他幾個鎖定位元組。
WAL_RECOVER_LOCK
WAL_RECOVER_LOCK 僅獨佔鎖定。WAL_RECOVER_LOCK 永遠不會取得共享鎖定。
任何執行復原以重建共享 WAL 索引的連線都持有獨佔 WAL_RECOVER_LOCK。
重建其私有堆疊記憶體 WAL 索引的唯讀連線不會持有此鎖定。(它不能,因為唯讀連線不被允許持有任何獨佔鎖定。)此鎖定僅在重建儲存在記憶體映射 SHM 檔案中的全域共享 WAL 索引時持有。
除了鎖定此位元組之外,執行復原的連線也會取得所有其他 WAL 鎖定的獨佔鎖定,但 WAL_READ_LOCK(0) 除外。
WAL_READ_LOCK(N)
共有五個獨立的讀取鎖定,編號為 0 到 4。讀取鎖定可以是 SHARED 或 EXCLUSIVE。連線會在交易期間取得其中一個讀取鎖定位元組的共享鎖定。連線也會在更新對應讀取標記值時,短暫取得讀取鎖定的獨佔鎖定,一次一個。執行復原時,會獨佔持有讀取鎖定 1 到 4。
每個讀取鎖定位元組對應到 WAL 索引標頭中位元組 100 到 119 的五個 32 位元讀取標記整數之一,如下所示
鎖定名稱 | 鎖定偏移量 | 讀取標記名稱 | 讀取標記偏移量 |
---|---|---|---|
WAL_READ_LOCK(0) | 123 | read-mark[0] | 100..103 |
WAL_READ_LOCK(1) | 124 | read-mark[1] | 104..107 |
WAL_READ_LOCK(2) | 125 | read-mark[2] | 108..111 |
WAL_READ_LOCK(3) | 126 | read-mark[3] | 112..115 |
WAL_READ_LOCK(4) | 127 | read-mark[4] | 116..119 |
當連線持有 WAL_READ_LOCK(N) 的共享鎖定時,表示連線保證會使用 WAL,而不是資料庫檔案,來取得 WAL 中前 read-mark[N] 個項目修改的任何資料庫頁面。read-mark[0] 永遠是零。如果連線持有 WAL_READ_LOCK(0) 的共享鎖定,表示連線預期可以忽略 WAL,並從主資料庫讀取任何需要的內容。如果 N>0,表示連線可以自由使用 WAL 檔案中超過 read-mark[N] 的部分(如果需要),直到前 mxFrame 個框架。但是,當連線持有 WAL_READ_LOCK(0) 的共享鎖定時,表示連線保證永遠不會從 WAL 讀取內容,而且會直接從主資料庫取得所有內容。
如果檢查點在執行時看到 WAL_READ_LOCK(N) 上有鎖定,則它不得將 WAL 內容移到主資料庫,超過前 read-mark[N] 個框架。如果這麼做,它會覆寫持有鎖定的程序預期能夠從主資料庫檔案讀取的內容。這會導致以下後果:如果 WAL 檔案包含超過 read-mark[N] 個框架(如果 mxFrame>read-mark[N],則表示 WAL_READ_LOCK(N) 由另一個程序持有),則檢查點無法執行完成。
當寫作者想要重設 WAL時,必須確保沒有任何鎖定在 WAL_READ_LOCK(N) 上,其中 N>0,因為此類鎖定表示其他連線仍在使用目前的 WAL 檔案,而WAL 重設會從這些其他連線中刪除內容。如果其他連線持有 WAL_READ_LOCK(0),則WAL 重設可以發生,因為透過持有 WAL_READ_LOCK(0),這些其他連線承諾不使用 WAL 中的任何內容。
進入和離開 WAL 模式
想要進入或離開 WAL 模式的連線必須持有 SQLITE_LOCK_EXCLUSIVE 鎖定。因此,進入 WAL 模式就像任何其他寫入交易,因為回滾模式中的每個寫入交易都需要 SQLITE_LOCK_EXCLUSIVE 鎖定。如果資料庫檔案已經在 WAL 模式中(因此如果想要將其變回回滾模式),且資料庫中有兩個或更多個連線,則這些連線中的每個連線都將持有 SQLITE_LOCK_SHARED 鎖定。這表示無法取得 SQLITE_LOCK_EXCLUSIVE,且不允許離開 WAL 模式。這可防止一個連線從另一個連線中刪除 WAL 模式。這也表示將資料庫從 WAL 模式移至回滾模式的唯一方法是關閉所有連線,只保留一個連線至資料庫。
關閉與 WAL 模式資料庫的連線
當資料庫連線關閉(透過sqlite3_close()或sqlite3_close_v2())時,會嘗試取得 SQLITE_LOCK_EXCLUSIVE。如果此嘗試成功,表示正在關閉的連線是資料庫的最後一個連線。在這種情況下,最好清除 WAL 和 WAL 索引檔案,因此正在關閉的連線會執行檢查點(同時持有 SQLITE_LOCK_EXCLUSIVE),並刪除 WAL 和 WAL 索引檔案。在 WAL 和 WAL 索引檔案都被刪除後,才會釋放 SQLITE_LOCK_EXCLUSIVE。
如果應用程式在關閉之前呼叫資料庫連線上的 sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL),則最終檢查點仍會執行,但 WAL 和 WAL 索引檔案不會像平常一樣刪除。這會讓資料庫處於允許其他沒有資料庫、WAL 或 WAL 索引檔案寫入權限的程序以唯讀方式開啟資料庫的狀態。如果 WAL 和 WAL 索引檔案遺失,則沒有權限建立和初始化這些檔案的程序將無法開啟資料庫,除非資料庫使用 immutable 查詢參數 指定為不可變。
在 復原 期間重建全域共用 WAL 索引
在 復原 期間重建全域共用 WAL 索引時,除了 WAL_READ_LOCK(0) 之外,所有 WAL 索引鎖定都會獨佔持有。
將新的交易附加到 WAL 的結尾
在將新框架新增到 WAL 檔案的結尾時,會獨佔持有 WAL_WRITE_LOCK。
在交易中從資料庫和 WAL 讀取內容
執行檢查點
重設 WAL 檔案
WAL 重設 表示將 WAL 倒回,並從頭開始新增新框架。這會在將新框架附加到 mxFrame 等於 nBackfill 且沒有鎖定 WAL_READ_LOCK(1) 到 WAL_READ_LOCK(4) 的 WAL 時發生。會持有 WAL_WRITE_LOCK。
復原是重建 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