數據庫 wal 模式 分析

對於以下錯誤的解決思路

 

看我的另外一篇文章

https://blog.csdn.net/tianxuhong/article/details/78752357

 android.database.sqlite.SQLiteCantOpenDatabaseException: unable to open database file (code 14)

 

▼ 目錄

1.概述

2. WAL如何工作

2.1。檢查點

2.2。併發

2.3。性能注意事項

3.激活和配置WAL模式

3.1。自動檢查點

3.2。應用程序啓動的檢查點

3.3。WAL模式的持久性

4. WAL文件

5.只讀數據庫

6.避免過大的WAL文件

7. WAL-Index共享內存的實現

8.使用沒有共享內存的WAL

9.有時查詢在WAL模式下返回SQLITE_BUSY

10.向後兼容性

1.概述

SQLite實現原子提交和回滾的默認方法 是回滾日誌。從版本3.7.0(2010-07-21)開始,可以使用新的“預寫日誌”選項(以下稱爲“WAL”)。

使用WAL而不是回滾日誌有利有弊。優點包括:

  1. 在大多數情況下,WAL明顯更快。
  2. WAL提供更多的併發性,因爲讀者不會阻止編寫者,而編寫者也不會阻止讀者。閱讀和寫作可以同時進行。
  3. 使用WAL,磁盤I / O操作往往更順序。
  4. WAL使用更少的fsync()操作,因此在fsync()系統調用被破壞的系統上不易受到問題的影響。

但也有缺點:

  1. WAL通常要求VFS 支持共享內存原語。(例外:沒有共享內存的WAL)內置的unix和Windows VFS支持此功能,但自定義操作系統的第三方擴展VFS可能不支持。
  2. 使用數據庫的所有進程必須位於同一臺主機上; WAL無法在網絡文件系統上運行。
  3. 涉及針對多個ATTACHed 數據庫的更改的事務對於每個單獨的數據庫都是原子的,但在所有數據庫中作爲集合不是原子的。
  4. 進入WAL模式後,無法在空數據庫上或使用VACUUM或使用備份API從備份恢復時更改page_size。您必須處於回滾日誌模式才能更改頁面大小。
  5. 無法打開只讀WAL數據庫。如果該文件存在,則打開過程必須具有與數據庫關聯的“ -shm ” wal-index共享內存文件的寫權限,如果“ -shm ”文件不存在,則必須對包含數據庫文件的目錄具有寫訪問權限。 從版本3.22.02018-01-22)開始,如果-shm和-wal文件已存在或者可以創建這些文件或 數據庫是不可變的,則可以打開只讀WAL模式數據庫文件。
  6. 在大多數讀取和很少寫入的應用程序中,WAL可能比傳統的rollback-journal方法稍慢(可能慢1%或2%)。
  7. 還有一個與每個數據庫關聯的額外準持久性“ -wal ”文件和“ -shm ”共享內存文件,這可能使SQLite不太適合用作 應用程序文件格式
  8. 檢查點的額外操作雖然默認是自動的,但仍然是應用程序開發人員需要注意的事情。
  9. WAL適用於較小的交易。WAL對於非常大的事務不適用。對於大於約100兆字節的事務,傳統的回滾日誌模式可能會更快。對於超過千兆字節的事務,WAL模式可能會因I / O或磁盤已滿錯誤而失敗。建議將其中一種回滾日誌模式用於大於幾十兆字節的事務。 從版本3.11.0(2016-02-15)開始,WAL模式與大型事務一樣有效,與回滾模式一樣。

2. WAL如何工作

傳統的回滾日誌通過將原始未更改的數據庫內容的副本寫入單獨的回滾日誌文件,然後將更改直接寫入數據庫文件來工作。如果發生崩潰或ROLLBACK,則回滾日誌中包含的原始內容將回放到數據庫文件中,以將數據庫文件還原爲其原始狀態。該COMMIT當回滾日誌被刪除時。

WAL方法將此反轉。原始內容保留在數據庫文件中,更改將附加到單獨的WAL文件中。甲COMMIT當指示一個提交一個特殊的記錄被追加到WAL發生。因此,COMMIT可以在不寫入原始數據庫的情況下發生,這允許讀者在將更改同時提交到WAL中時繼續從原始未更改的數據庫進行操作。多個事務可以附加到單個WAL文件的末尾。

2.1。檢查點

當然,人們希望最終將WAL文件中附加的所有事務傳回原始數據庫。將WAL文件事務移回數據庫稱爲“ 檢查點 ”。

 

考慮回滾和預寫日誌之間區別的另一種方法是,在rollback-journal方法中,有兩個基本操作,即讀取和寫入,而使用預寫日誌,現在有三個基本操作:讀取和寫入和檢查點。

默認情況下,當WAL文件達到​​1000頁的閾值大小時,SQLite會自動執行檢查點。( SQLITE_DEFAULT_WAL_AUTOCHECKPOINT編譯時選項可用於指定不同的默認值。)使用WAL的應用程序不必執行任何操作以使這些檢查點發生。但如果他們願意,應用程序可以調整自動檢查點閾值。或者他們可以在空閒時刻或單獨的線程或過程中關閉自動檢查點並運行檢查點。

2.2。併發

當在WAL模式數據庫上開始讀取操作時,它首先會記住WAL中最後一個有效提交記錄的位置。稱此爲“結束標記”。由於WAL可以在各種讀取器連接到數據庫時增長並添加新的提交記錄,因此每個讀取器都可能具有自己的結束標記。但對於任何特定的讀者,結束標記在事務持續期間不變,從而確保單個讀取事務僅查看單個時間點存在的數據庫內容。

當讀者需要一頁內容時,它首先檢查WAL以查看該頁面是否出現,如果是,它會在讀取器結束標記之前拉入WAL中發生的頁面的最後一個副本。如果在讀取器結束標記之前WAL中沒有頁面副本,則從原始數據庫文件中讀取頁面。讀者可以存在於不同的進程中,因此爲了避免強迫每個讀者掃描整個WAL尋找頁面(WAL文件可以增長到幾兆字節,具體取決於檢查點運行的頻率),稱爲“wal-index”的數據結構保存在共享內存中,幫助讀者快速定位WAL中的頁面,並且I / O最少。wal-index極大地提高了讀者的性能,但共享內存的使用意味着所有讀者必須存在於同一臺機器上。

作者只是將新內容附加到WAL文件的末尾。因爲作家不做任何會干擾讀者行爲的事情,作家和讀者可以同時運行。但是,由於只有一個WAL文件,因此一次只能有一個寫入器。

檢查點操作從WAL文件中獲取內容並將其傳回原始數據庫文件。檢查點可以與讀取器同時運行,但檢查點必須在到達WAL中超過任何當前讀取器結束標記的頁面時停止。檢查點必須在此時停止,否則它可能會覆蓋讀取器正在使用的部分數據庫文件。檢查點會記住(在wal-index中)它到達了多遠,並將繼續將內容從WAL傳輸到數據庫,從下次調用時停止。

因此,長時間運行的讀取事務可以防止檢查指針進行。但據推測,每次讀取事務最終都會結束,並且checkpointer將能夠繼續。

每當發生寫操作時,編寫器檢查checkpointer進行了多少進度,如果整個WAL已經轉移到數據庫並同步,如果沒有讀者正在使用WAL,那麼編寫器將把WAL倒回到開始並開始在WAL的開頭放置新的交易。此機制可防止WAL文件無限制地增長。

2.3。性能注意事項

寫入事務非常快,因爲它們只涉及一次寫入內容(而不是兩次用於回滾日誌事務),並且因爲寫入都是順序的。此外,只要應用程序願意在斷電或硬重啓後犧牲耐用性,就不需要將內容同步到磁盤。(如果PRAGMA synchronous設置爲FULL,則寫入者在每次事務提交時同步WAL, 但如果PRAGMA synchronous設置爲NORMAL,則省略此同步 。)

另一方面,隨着WAL文件的大小增加,讀取性能下降,因爲每個讀取器必須檢查WAL文件中的內容,並且檢查WAL文件所需的時間與WAL文件的大小成比例。wal-index有助於更快地找到WAL文件中的內容,但隨着WAL文件大小的增加,性能仍會下降。因此,爲了保持良好的讀取性能,通過定期運行檢查點來保持WAL文件大小不變是很重要的。

檢查點確實需要同步操作,以避免在斷電或硬重啓後數據庫損壞的可能性。在將內容從WAL移動到數據庫之前,必須將WAL同步到持久存儲,並且必須在重置WAL之前同步數據庫文件。檢查站還需要更多的追求。checkpointer努力儘可能多地對數據庫執行順序頁面寫入(頁面按照升序從WAL傳輸到數據庫),但即便如此,頁面寫入中通常會有許多搜索操作。這些因素結合起來使檢查點比寫入事務更慢。

默認策略是允許連續寫入事務使WAL增長,直到WAL變爲大約1000頁,然後爲每個後續COMMIT運行檢查點操作,直到WAL重置爲小於1000頁爲止。默認情況下,檢查點將由執行COMMIT的同一線程自動運行,該線程將WAL推過其大小限制。這會導致大多數COMMIT操作非常快,但偶爾的COMMIT(觸發檢查點的那些)要慢得多。如果不希望出現這種影響,那麼應用程序可以禁用自動檢查點並在單獨的線程或單獨的進程中運行定期檢查點。(完成此任務的命令和接口鏈接 如下所示。)

請注意,在PRAGMA同步設置爲NORMAL的情況下,檢查點是發出I / O屏障或同步操作的唯一操作(Windows上的unix或FlushFileBuffers()上的fsync())。因此,如果應用程序在單獨的線程或進程中運行檢查點,則執行數據庫查詢和更新的主線程或進程將永遠不會阻止同步操作。這有助於防止在繁忙的磁盤驅動器上運行的應用程序中出現“閂鎖”。此配置的缺點是事務不再持久,並且可能在電源故障或硬重置後回滾。

另請注意,平均讀取性能和平均寫入性能之間存在折衷。爲了最大化讀取性能,人們希望保持WAL儘可能小,因此經常運行檢查點,可能與每個COMMIT一樣頻繁。爲了最大限度地提高寫入性能,人們希望通過儘可能多的寫入來分攤每個檢查點的成本,這意味着想要不經常運行檢查點並讓WAL在每個檢查點之前儘可能大。因此,根據應用程序的相對讀寫性能要求,決定運行檢查點的頻率可能因應用程序而異。WAL達到1000頁後,默認策略是運行檢查點,這種策略似乎在工作站上的測試應用程序中運行良好,

3.激活和配置WAL模式

SQLite數據庫連接默認爲 journal_mode = DELETE。要轉換爲WAL模式,請使用以下編譯指示:

PRAGMA journal_mode = WAL;

journal_mode pragma返回一個字符串,它是新的日誌模式。成功時,pragma將返回字符串“ wal ”。如果無法完成到WAL的轉換(例如,如果VFS 不支持必要的共享內存原語),則日記模式將保持不變,並且從原語返回的字符串將是先前的日記模式(例如“ 刪除 “)。

3.1。自動檢查點

默認情況下,每當發生COMMIT 導致WAL文件大小爲1000頁或更大,或者數據庫文件上的最後一個數據庫連接關閉時,SQLite將自動檢查點。默認配置適用於大多數應用程序。但是需要更多控制的程序可以使用wal_checkpoint pragma或調用sqlite3_wal_checkpoint() C接口強制檢查點。可以使用wal_autocheckpoint pragma或通過調用 sqlite3_wal_autocheckpoint() C接口來更改自動檢查點閾值或完全禁用自動檢查點。程序也可以使用sqlite3_wal_hook()註冊任何事務提交到WAL時要調用的回調。然後,此回調可以根據其認爲合適的標準調用 sqlite3_wal_checkpoint()sqlite3_wal_checkpoint_v2()。(自動檢查點機制是作爲sqlite3_wal_hook()的簡單包裝器實現的。)

3.2。應用程序啓動的檢查點

應用程序只需調用sqlite3_wal_checkpoint()sqlite3_wal_checkpoint_v2()即可使用數據庫上的任何可寫數據庫連接啓動檢查點 。檢查點有三種子類型,其攻擊性各不相同:PASSIVE,FULL和RESTART。默認檢查點樣式是PASSIVE,它可以在不干擾其他數據庫連接的情況下儘可能多地工作,如果存在併發讀取器或編寫器,則可能無法完成。由sqlite3_wal_checkpoint()和自動檢查點機制啓動的所有檢查點都是PASSIVE。FULL和RESTART檢查點更難以運行檢查點完成,只能通過調用sqlite3_wal_checkpoint_v2()來啓動。有關FULL和RESET檢查點的其他信息,請參閱 sqlite3_wal_checkpoint_v2()文檔。

3.3。WAL模式的持久性

與其他日記模式不同, PRAGMA journal_mode = WAL是持久的。如果進程設置WAL模式,然後關閉並重新打開數據庫,則數據庫將以WAL模式返回。相反,如果進程設置(例如)PRAGMA journal_mode = TRUNCATE然後關閉並重新打開,則數據庫將以DELETE的默認回滾模式而不是之前的TRUNCATE設置重新啓動。

WAL模式的持久性意味着可以在WAL模式下將應用程序轉換爲使用SQLite,而無需對應用程序本身進行任何更改。只需使用命令行shell或其他實用程序在數據庫文件上運行“ PRAGMA journal_mode = WAL; ” ,然後重新啓動應用程序。

如果在任何一個連接上設置了WAL日誌模式,則將在同一數據庫文件的所有連接上設置WAL日誌模式。

4. WAL文件

在WAL模式數據庫上打開數據庫連接時,SQLite維護一個名爲“Write Ahead Log”或“WAL File”的額外日誌文件。磁盤上此文件的名稱通常是帶有額外“ -wal ”後綴的數據庫文件的名稱,但如果使用SQLITE_ENABLE_8_3_NAMES編譯SQLite,則可能會應用不同的命名規則。

只要任何數據庫連接打開數據庫,WAL文件就存在。通常,當最後一次連接數據庫關閉時,WAL文件會自動刪除。但是,如果打開數據庫的最後一個進程退出而沒有乾淨地關閉數據庫連接,或者如果 SQLITE_FCNTL_PERSIST_WAL 文件控制使用,然後在關閉所有數據庫連接後,WAL文件可能會保留在磁盤上。WAL文件是數據庫持久狀態的一部分,如果複製或移動數據庫,則應與數據庫保持一致。如果數據庫文件與其WAL文件分離,則先前提交到數據庫的事務可能會丟失,或者數據庫文件可能已損壞。刪除WAL文件的唯一安全方法是使用sqlite3_open()接口之一打開數據庫文件,然後使用sqlite3_close()立即關閉數據庫。

WAL文件格式的精確定義,是跨平臺的。

5.只讀數據庫

較舊版本的SQLite無法讀取只讀的WAL模式數據庫。換句話說,爲了讀取WAL模式數據庫,需要寫訪問。從SQLite 版本3.22.02018-01-22)開始放寬了這個約束。

在較新版本的SQLite上,只要滿足以下一個或多個條件,仍然可以讀取只讀介質上的WAL模式數據庫或缺少寫入權限的WAL模式數據庫:

  1. 該-shm和-wal文件已經存在,並且是可讀
  2. 對包含數據庫的目錄具有寫權限,以便可以創建-shm和-wal文件。
  3. 使用不可變查詢參數打開數據庫連接 。

儘管可以打開只讀WAL模式數據庫,但最好在將SQLite數據庫映像刻錄到只讀介質之前轉換爲 PRAGMA journal_mode = DELETE

6.避免過大的WAL文件

在正常情況下,新內容被附加到WAL文件,直到WAL文件累積大約1000頁(因此大小約爲4MB),此時檢查點自動運行並且WAL文件被回收。檢查點通常不會截斷WAL文件(除非設置了journal_size_limit pragma)。相反,它只會導致SQLite從頭開始覆蓋WAL文件。這樣做是因爲覆蓋現有文件通常比追加更快。當數據庫的最後一個連接關閉時,該連接會執行最後一個檢查點,然後刪除WAL及其關聯的共享內存文件,以清理磁盤。

所以在絕大多數情況下,應用程序根本不用擔心WAL文件。SQLite會自動處理它。但是有可能讓SQLite進入WAL文件無限制增長的狀態,導致磁盤空間使用過多,查詢速度變慢。以下項目符號列舉了一些可能發生這種情況的方法以及如何避免這些方法。

  • 禁用自動檢查點機制。 在默認配置中,當WAL文件長度超過1000頁時,SQLite將在任何事務結束時檢查WAL文件。但是,存在可以禁用或推遲此自動檢查點的編譯時和運行時選項。如果應用程序禁用自動檢查點,則沒有什麼可以阻止WAL文件過度增長。

  • 檢查站飢餓。 如果使用WAL文件沒有其他數據庫連接,則檢查點只能運行完成並重置WAL文件。如果另一個連接打開了讀取事務,則檢查點無法重置WAL文件,因爲這樣做可能會從讀取器下方刪除內容。檢查點將儘可能多地完成工作而不會擾亂讀者,但它無法完成。在下一次寫入事務之後,檢查點將再次啓動。這種情況一直持續到某些檢查點能夠完成爲止。

    但是,如果數據庫具有許多併發重疊讀取器並且始終至少有一個活動讀取器,則無法檢查點完成,因此WAL文件將無限制地增長。

    通過確保存在“讀取器間隙”可以避免這種情況:沒有進程從數據庫讀取的時間以及在這些時間內嘗試檢查點的時間。在具有許多併發讀取器的應用程序中,可以考慮使用SQLITE_CHECKPOINT_RESTARTSQLITE_CHECKPOINT_TRUNCATE選項運行手動檢查點,這將確保檢查點在返回之前運行完成。使用SQLITE_CHECKPOINT_RESTARTSQLITE_CHECKPOINT_TRUNCATE的缺點 是讀者可能會在檢查點運行時阻塞。

  • 非常大的寫入事務。 檢查點只能在沒有其他事務正在運行時完成,這意味着WAL文件無法在寫入事務中重置。因此,對大型數據庫進行大量更改可能會導致大型WAL文件。寫入事務完成後,WAL文件將被檢查點(假設沒有其他讀者阻止它),但與此同時,文件可能會變得非常大。

    從SQLite 版本3.11.0(2016-02-15)開始,單個事務的WAL文件大小應與事務本身成比例。事務更改的頁面只能寫入WAL文件一次。但是,對於舊版本的SQLite,如果事務變得大於頁面緩存,則可能會多次將同一頁面寫入WAL文件。

7. WAL-Index共享內存的實現

沃爾瑪指數是否使用了mmapped健壯性一個普通文件實現。WAL模式的早期(預發佈)實現將wal-index存儲在易失性共享內存中,例如在Linux上的/ dev / shm或其他unix系統上的/ tmp中創建的文件。該方法的問題是具有不同根目錄的進程(通過chroot更改))將看到不同的文件,因此使用不同的共享內存區域,導致數據庫損壞。用於創建無名共享內存塊的其他方法不能在各種版本的unix中移植。我們找不到任何方法來在Windows上創建無名共享內存塊。我們發現保證訪問同一數據庫文件的所有進程使用相同共享內存的唯一方法是通過將文件映射到與數據庫本身相同的目錄來創建共享內存。

使用普通磁盤文件提供共享內存的缺點是,通過將共享內存寫入磁盤,它實際上可能會執行不必​​要的磁盤I / O. 然而,開發人員並不認爲這是一個主要問題,因爲沃爾瑪指數的規模很少超過32 KiB並且從未同步過。此外,當最後一個數據庫連接斷開連接時,將刪除wal-index後備文件,這通常會阻止任何真正的磁盤I / O發生。

共享內存的默認實現不可接受的專用應用程序可以通過自定義VFS設計替代方法。例如,如果已知某個特定數據庫只能由單個進程中的線程訪問,則可以使用堆內存而不是真正的共享內存來實現wal-index。

8.使用沒有共享內存的WAL

從SQLite 版本3.7.4(2010-12-07)開始,只要在第一次嘗試訪問之前將locking_mode設置爲EXCLUSIVE,即使共享內存不可用,也可以創建,讀取和寫入WAL數據庫 。換句話說,如果保證該進程是訪問數據庫的唯一進程,則進程可以與WAL數據庫交互而不使用共享內存。此功能允許由缺少sqlite3_io_methods對象上的“版本2”共享內存方法xShmMap,xShmLock,xShmBarrier和xShmUnmap 的舊VFS創建,讀取和寫入WAL數據庫。

如果 在第一次WAL模式數據庫訪問之前設置了EXCLUSIVE鎖定模式,那麼SQLite永遠不會嘗試調用任何共享內存方法,因此不會創建共享內存wal-index。在這種情況下,只要日誌模式爲WAL,數據庫連接就保持在EXCLUSIVE模式; 嘗試使用“ PRAGMA locking_mode = NORMAL; ” 更改鎖定模式是no-ops。更改EXCLUSIVE鎖定模式的唯一方法是首先更改WAL日誌模式。

如果NORMAL鎖定模式對第一個WAL模式數據庫訪問有效,則創建共享內存wal-index。這意味着底層VFS必須支持“版本2”共享內存。如果VFS不支持共享內存方法,則嘗試打開已處於WAL模式的數據庫或嘗試將數據庫轉換爲WAL模式將失敗。只要一個連接使用共享內存wal-index,就可以在NORMAL和EXCLUSIVE之間自由更改鎖定模式。僅當省略共享內存wal-index時,在第一個WAL模式數據庫訪問之前鎖定模式爲EXCLUSIVE時,鎖定模式纔會停留在EXCLUSIVE中。

9.有時查詢在WAL模式下返回SQLITE_BUSY

WAL模式第二個優點是編寫者不會阻止讀者和讀者阻止編寫者。這基本上是正確的。但是有一些模糊的情況,對WAL模式數據庫的查詢可以返回SQLITE_BUSY,因此應用程序應該爲這種偶然事件做好準備。

針對WAL模式數據庫的查詢可以返回SQLITE_BUSY的情況 包括以下內容:

  • 如果另一個數據庫連接以獨佔鎖定模式打開數據庫模式,則對數據庫的所有查詢都將返回SQLITE_BUSY。Chrome和Firefox都以獨佔鎖定模式打開數據庫文件,因此在應用程序運行時嘗試讀取Chrome或Firefox數據庫會遇到此問題。

  • 當特定數據庫的最後一個連接關閉時,該連接將在清理WAL和共享內存文件時獲得短時間的獨佔鎖。如果第二個數據庫在第一個連接仍處於清理過程的中間時嘗試打開並查詢數據庫,則第二個連接可能會收到SQLITE_BUSY 錯誤。

  • 如果與數據庫的最後一次連接崩潰,則第一個打開數據庫的新連接將啓動恢復過程。在恢復期間保持獨佔鎖定。因此,如果第二個連接正在運行恢復時第三個數據庫連接嘗試跳入並進行查詢,則第三個連接將收到SQLITE_BUSY錯誤。

10.向後兼容性

WAL模式的數據庫文件格式保持不變。但是,WAL文件和wal-index是新概念,因此舊版本的SQLite將不知道如何恢復崩潰發生時在WAL模式下運行的崩潰的SQLite數據庫。防止舊版本的SQLite(版本3.7.0,2010-07-22之前)嘗試恢復WAL模式數據庫(並且更糟糕)數據庫文件格式版本號(數據庫頭中的字節18和19)在WAL模式下,從1增加到2。因此,如果舊版本的SQLite嘗試連接到以WAL模式運行的SQLite數據庫,它將報告“文件已加密或不是數據庫”的錯誤。

可以使用如下的pragma顯式更改出WAL模式:

PRAGMA journal_mode = DELETE;

故意更改出WAL模式會將數據庫文件格式版本號更改回1,以便舊版本的SQLite可以再次訪問數據庫文件。

  1. 概述
    在3.7.0以後,WAL(Write-Ahead Log)模式可以使用,是另一種實現事務原子性的方法。
    • WAL的優點
      1. 在大多數情況下更快
      2. 並行性更高。因爲讀操作和寫操作可以並行。
      3. 文件IO更加有序化,串行化(more sequential)
      4. 使用fsync()的次數更少,在fsync()調用時好時壞的機器上較爲未定。
    • 缺點
      1. 一般情況下需要VFS支持共享內存模式。(shared-memory primitives)
      2. 操作數據庫文件的進程必須在同一臺主機上,不能用在網絡操作系統。
      3. 持有多個數據庫文件的數據庫連接對於單個數據庫時原子的,對於全部數據庫是不原子的。
      4. 進入WAL模式以後不能修改page的size。
      5. 不能打開只讀的WAL數據庫(Read-Only Databases),這進程必須有"-shm"文件的寫權限。
      6. 對於只進行讀操作,很少進行寫操作的數據庫,要慢那麼1到2個百分點。
      7. 會有多餘的"-wal"和"-shm"文件
      8. 需要開發者注意checkpointing
  2. 原理

    回滾日誌的方法是把爲改變的數據庫文件內容寫入日誌裏,然後把改變後的內容直接寫到數據庫文件中去。在系統crash或掉電的情況下,日誌裏的內容被重新寫入數據庫文件中。日誌文件被刪除,標誌commit着一次commit的結束。

    WAL模式於此此相反。原始爲改變的數據庫內容在數據庫文件中,對數據庫文件的修改被追加到單獨的WAL文件中。當一條記錄被追加到WAL文件後,標誌着一次commit的結束。因此一次commit不必對數據庫文件進行操作,當正在進行寫操作時,可以同時進行讀操作。多個事務的內容可以追加到一個WAL文件的末尾。

    1. checkpoint
      最後WAL文件的內容必須更新到數據庫文件中。把WAL文件的內容更新到數據庫文件的過程叫做一次checkpoint
      回滾日誌的方法有兩種操作:讀和寫。WAL有三種操作,讀、寫和checkpoint。
      默認的,SQL會在WAL文件達到1000page時進行一次checkpoint。進行WAL的時機也可以由應用程序自己決定。
    2. 併發性
      當一個讀操作發生在WAL模式的數據庫上時,會首先找到WAL文件中最後一次提交,叫做"end mark"。每一個事務可以有自己的"end point",但對於一個給定額事務來說,end mark是固定的。
      當讀取數據庫中的page時,SQLite會先從WAL文件中尋找有沒有對應的page,從找出離end mark最近的那一條記錄;如果找不到,那麼就從數據庫文件中尋找對一個的page。爲了避免每次事務都要掃描一遍WAL文件,SQLite在共享內存中維護了一個"wal-index"的數據結構,幫助快速定位page。
      寫數據庫只是把新內容加到WAL文件的末尾,和讀操作沒有關係。由於只有一個WAL文件,因此同時只能有一個寫操作。
      checkpoint操作可以和讀操作並行。但是如果checkpoint把一個page寫入數據庫文件,而且這個page超過了當前讀操作的end mark時,checkpoint必須停止。否則會把當前正在讀的部分覆蓋掉。下次checkpoint時,會從這個page開始往數據庫中拷貝數據。
      當寫操作時,會檢查WAL文件被拷貝到數據庫的進度。如果已經完全被拷貝到數據庫文件中,已經同步,並且沒有讀操作在使用WAL文件,那麼會把WAL文件清空,從其實開始追加數據。保證WAL文件不會無限制增長。
    3. 性能
      寫操作是很快的,因爲只需要進行一次寫操作,並且是順序的(不是隨機的,每次都寫到末尾)。而且,把數據刷到磁盤上是不必須的。(如果PRAGMA synchronous是FULL,每次commit要刷一次,否則不刷。)
      讀操作的性能有所下降,因爲需要從WAL文件中查找內容,花費的時間和WAL文件的大小有關。wal-index可以縮短這個時間,但是也不能完全避免。因此需要保證WAL文件的不會太大。
      爲了保護數據庫不被損壞,需要在把WAL文件寫入數據庫之前把WAL文件刷入磁盤;在重置WAL文件之前要把數據庫內容刷入數據庫文件。此外checkpoint需要查找操作。這些因素使得checkpoint比寫操作慢一些。
      默認策略是很多線程可以增長WAL文件。把WAL文件大小變得比1000page大的那個線程要負責進行checkpoint。會導致絕大部分讀寫操作都是很快的,隨機有一個寫操作非常慢。也可以禁用自動checkpoint的策略,定期在一個線程或進程中進行checkpoint操作。
      高效的寫操作希望WAL文件越大越好;高效的讀操作希望WAL文件越小越好。兩者存在一個tradeoff。
  3. 激活和配置WAL模式
    PRAGMA journal_mode=WAL;,如果成功,會返回"wal"。

    1. 自動checkpoint
      可以手動checkpoint

      sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb)
      

      配置checkpoint

      sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
      
    2. Application-Initiated Checkpoints
      可以在任意一個可以進行寫操作的數據庫連接中調用sqlite3_wal_checkpoint_v2()sqlite3_wal_checkpoint()

    3. WAL模式的持久性
      當一個進程設置了WAL模式,關閉這個進程,重新打開這個數據庫,仍然是WAL模式。
      如果在一個數據庫連接中設置了WAL模式,那麼這個數據庫的所有連接都將被設爲WAL模式。

  4. 只讀數據庫
    如果數據庫需要恢復,而你只有讀權限,沒有寫權限,那麼你不能讀取這個數據庫,因爲進行讀操作的第一步就是恢復數據庫。
    類似的,因爲WAL模式下的數據庫進行讀操作時,需要類似數據庫恢復的操作,因此如果只有讀權限,也不能對打開數據庫。
    WAL的實現需要有一個基於WAL文件的哈希表在共享內存中。在Unix和Windows的VFS實現中,是基於MMap的。將共享內存映射到同目錄下的"-shm"文件中。因此即使是對WAL模式下的數據庫文件進行讀操作,也需要寫權限。
    爲了把數據庫文件轉化爲只讀的文件,需要先把這個數據庫的日誌模式改爲"delete".

  5. 避免過大的WAL文件

  6. WAL-index的共享內存實現
    在WAL發佈之前,曾經嘗試過將wal-index映射到臨時目錄,如/dev/shm或/tmp。但是不同的用戶看到的目錄是不同的,所以此路不通。
    後來嘗試將wal-index映射到匿名的虛擬內存塊中,但是無法在不用的Unix版本中保持一致。
    最終決定採用將wal-index映射到同目錄下。這樣子會導致不必要的磁盤IO。但是問題不大,是因爲wal-index很少超過32k,而且從不會調用sync操作。此外,最後一個數據庫連接關閉以後,這個文件會被刪除。
    如果這個數據庫只會被一個進程使用,那麼可以使用heap memory而不是共享內存。

  7. 不用共享內存實現WAL
    在3.7.4版本以後,只要SQLite的lock mode被設爲EXCLUSIVE,那麼即使共享內存不支持,也可以使用WAL模式。
    換句話說,如果只有一個進程使用SQLite,那麼不用共享內存也可以使用WAL。
    此時,將lock mode改爲normal是無效的,需要實現取消WAL模式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章