此 API 透過 localStorage/sessionStorage 以及在相容的瀏覽器中透過 來源私有檔案系統 提供資料庫持久性。
⚠️ 注意:無痕模式和訪客瀏覽模式的限制
大多數瀏覽器都提供「無痕」和/或「訪客」瀏覽模式,這些模式會特意更改或停用瀏覽器的某些功能。在這種模式下執行時,儲存功能可能會受到負面影響,例如配額較低或完全缺乏持久性。確切的限制因瀏覽器而異,但在此頁面上描述的持久性功能在這種「隱身」模式下執行時,可能會比文件中描述的更受限,甚至可能完全無法使用,這並不完全出乎意料。
「我們如何預先偵測這些情況?」是一個合理的問題,但瀏覽器製造商故意使其難以偵測此類模式,以防止網站限制無痕模式使用者的存取權等情況。在任何特定瀏覽器中偵測此情況的任何現有方法都可能很快就會過時,因為瀏覽器製造商會注意到並更改相關設定,使此類模式對已造訪的網站更加不透明,因此我們無法提供任何規避它們的建議。
鍵值虛擬檔案系統 (kvvfs):localStorage
和 sessionStorage
kvvfs
是一個 sqlite3_vfs 實作,旨在將整個 sqlite3 資料庫儲存在 localStorage
或 sessionStorage
物件中。這些物件僅在主 UI 執行緒中可用,在 Worker 執行緒中不可用,因此此功能僅在主執行緒中可用。 kvvfs
將資料庫的每個頁面儲存在儲存物件的單獨項目中,並將每個資料庫頁面編碼為 ASCII 格式,使其與 JS 相容。
此虛擬檔案系統**每個儲存物件僅支援單個資料庫**。也就是說,最多可以有一個 localStorage
資料庫和一個 sessionStorage
資料庫。
要使用它,請將虛擬檔案系統名稱「kvvfs」傳遞給任何接受虛擬檔案系統名稱的資料庫開啟常式。資料庫的檔案名稱*必須*是 local
或 session
,或是它們的別名 :localStorage:
和 :sessionStorage:
。任何其他名稱都會導致資料庫開啟失敗。使用 URI 樣式名稱 時,請使用以下其中一種:
file:local?vfs=kvvfs
file:session?vfs=kvvfs
在主 UI 執行緒中載入時,以下工具方法會新增到 sqlite3.capi
命名空間:
sqlite3_js_kvvfs_size(which='')
傳回 kvvfs 使用的儲存空間位元組數的*估計值*。sqlite3_js_kvvfs_clear(which='')
清除所有 kvvfs 擁有的狀態,並傳回它刪除的記錄數(每個資料庫頁面一個記錄)。
在這兩種情況下,引數可以是 ("local"
、"session"
、""
) 其中之一。在前兩種情況下,僅作用於 localStorage
或 sessionStorage
,在後一種情況下,兩者都會作用。
儲存限制很小:通常為 5MB,請注意 JS 使用雙位元組字元編碼,因此實際儲存空間小於此值。將資料庫編碼為 JS 可用的格式速度緩慢且佔用大量空間,因此不建議將這些儲存選項用於任何「正式工作」。相反地,新增它們主要是為了讓不支援 OPFS 的用戶端至少擁有某種形式的持久性。
儲存空間滿時,修改資料庫的資料庫操作將會失敗。由於將資料庫儲存在持久性 JS 物件中本身效率低下,需要以文字形式編碼,因此 kvvfs 中的資料庫比其磁碟上的對應資料庫更大,速度也更慢(就計算而言,儘管對許多用戶端來說,感知效能可能夠快)。
JsStorageDb:簡化 kvvfs 的使用
使用 OO1 API 可以更輕鬆地使用 kvvfs。詳情請參閱 JsStorageDb 類別。
將資料庫匯入 kvvfs
將現有資料庫匯入 kvvfs 最直接的方法是使用另一個資料庫中的 VACUUM INTO
。例如
let db = new sqlite3.oo1.DB();
db.exec("create table t(a); insert into t values(1),(2),(3)");
db.exec("VACUUM INTO 'file:local?vfs=kvvfs'");
// Will fail if there's already a localStorage kvvfs:
// sqlite3.js:14022 sqlite3_step() rc= 1 SQLITE_ERROR SQL = VACUUM INTO 'file:local?vfs=kvvfs'
// But we can fix that by clearing the storage:
sqlite3.capi.sqlite3_js_kvvfs_clear('local');
// Then:
db.exec("VACUUM INTO 'file:local?vfs=kvvfs'");
db.close();
let ldb = new sqlite3.oo1.JsStorageDb('local');
ldb.selectValues('select a from t order by a'); // ==> [1,2,3]
同源私有檔案系統 (OPFS)
同源私有檔案系統,OPFS 是一種提供瀏覽器端持久性儲存的 API,sqlite3 可以使用它來儲存資料庫,這並非巧合1。
OPFS 僅在 Worker 執行緒環境中可用,主 UI 執行緒中不可用。
截至 2023 年 7 月,已知下列瀏覽器具有必要的 API
- 大約自 2022 年年中以來發行的基於 Chromium 的瀏覽器。從 v108(2022 年 11 月)開始,某些 OPFS API 從非同步變為同步,這會影響用戶端程式碼(即此程式庫)的處理方式。
- Firefox v111(2023 年 3 月)及更高版本
- Safari 16.4(2023 年 3 月)及更高版本
此程式庫提供多種在 OPFS 中儲存資料庫的解決方案,每種方案都有不同的取捨。
透過 sqlite3_vfs 使用 OPFS
僅當從 Worker 執行緒載入 sqlite3.js
時,才支援此功能,無論是在其專用 Worker 中載入,還是在與用戶端程式碼一起的 Worker 中載入。此 OPFS 包裝器完全使用 JavaScript 實作 sqlite3_vfs 包裝器。
如果瀏覽器似乎具有支援此功能的必要 API,則會自動啟用此功能。可以使用以下其中一種方法在 JS 程式碼中進行測試:
if(sqlite3.capi.sqlite3_vfs_find("opfs")){ ... OPFS VFS is available ... }
// Alternately:
if(sqlite3.oo1.OpfsDb){ ... OPFS VFS is available ... }
如果可用,名為「opfs」的 VFS 可以與任何接受 VFS 名稱的 sqlite3 API 一起使用,例如 sqlite3_vfs_find()
、sqlite3_db_open_v2()
和 sqlite3.oo1.DB
建構函式,請注意 OpfsDb
是 oo1.DB
的便捷子類別,它會自動使用此 VFS。對於 URI 樣式的名稱,請使用 file:my.db?vfs=opfs
。
⚠️注意:Safari 版本 < 17
Safari 17 以前的版本與目前的 OPFS VFS 實作不相容,因為瀏覽器處理來自子 Worker 的儲存空間時出現錯誤,而此錯誤沒有解決方法。SharedAccessHandle 集區 VFS 和 WASMFS 支援 都提供了替代方案,應該適用於 Safari 16.4 或更高版本。
⚠️注意:COOP 和 COEP HTTP 標頭
為了提供一定程度的透明並行資料庫存取支援,OPFS VFS 需要 JavaScript 的 SharedArrayBuffer
類型,而只有當 Web 伺服器在傳遞指令碼時包含所謂的 COOP 和 COEP 回應標頭,該類別才可用
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
如果沒有這些標頭,SharedArrayBuffer
將無法使用,因此 OPFS VFS 將無法載入。該類別是協調 sqlite3_vfs OPFS 代理的同步和非同步部分之間的通訊所必需的。
COEP 標頭的值也可以是 credentialless
,但這是否適用於任何給定的應用程式取決於它如何使用其他遠端資源。
如何發出這些標頭取決於底層的網路伺服器。
Apache 網路伺服器
對於 Apache 網路伺服器,這些標頭的設定方式如下所示:
<Location "/">
Header always append Cross-Origin-Embedder-Policy "require-corp"
Header always append Cross-Origin-Opener-Policy: "same-origin"
AddOutputFilterByType DEFLATE application/wasm
</Location>
Althttpd 網路伺服器
對於 althttpd 網路伺服器,請使用 --enable-sab
旗標啟動它(「sab」是 SharedArrayBuffer 的縮寫)。
Cloudflare Pages
請參閱 https://developers.cloudflare.com/pages/configuration/headers。
其他網路伺服器
如果您知道如何在其他網路伺服器中設定 COOP/COEP 標頭,請在 SQLite 論壇 上告知我們,我們會將這些資訊更新到文件中。
資料庫名稱中的目錄部分
與大多數 sqlite3_vfs 實作不同,如果資料庫是以「create」旗標開啟的,這個實作會自動建立資料庫檔案名稱的任何前導目錄部分。這種與常見慣例的差異是為了……
- 讓網路開發人員更輕鬆。
- 避免將 OPFS 特定的目錄建立 API 暴露給用戶端程式碼。理想情況下,用戶端資料庫相關程式碼應該與使用的儲存無關。
例如:
const db = new sqlite3.oo1.OpfsDb('/path/to/my.db','c');
如果需要,將會建立 /path/to
目錄。沒有前導斜線的路徑在功能上是等效的,從 OPFS 根目錄開始。
將資料庫匯入 OPFS
請參閱 OpfsDb 文件。
並行處理和檔案鎖定
⚠️**預先警告:** 並行存取 OPFS 託管的檔案是此 VFS 的一個痛點。用戶端應用程式將無法在此環境中實現桌面應用程式級別的並行性,但在瀏覽器分頁和/或 Workers 之間可以實現一定程度的並行性。
背景:OPFS 提供了一些此 API 所需的同步 API。可以在非同步模式下開啟檔案而無需任何鎖定,但存取同步 API 需要 OPFS 所謂的「同步存取控制代碼」,它會*獨佔鎖定*檔案。只要 OPFS 檔案被鎖定,在同一個 HTTP 源 中執行的任何其他服務都無法開啟該檔案。例如,只要檔案被另一個來自相同源的索引標籤鎖定,在一個瀏覽器索引標籤中執行的程式碼就無法存取該檔案。
*實質上*,這意味著沒有兩個資料庫控制代碼可以同時開啟同一個 OPFS 託管的資料庫。如果在兩個索引標籤中開啟同一個頁面,第二個索引標籤會在嘗試開啟同一個 OPFS 託管的資料庫時遇到鎖定錯誤!
為了減輕在多個索引標籤或工作執行緒中執行的 sqlite3 執行個體之間的衝突,sqlite3 僅在資料庫 API 需要鎖定時才取得寫入模式控制代碼。如果無法取得鎖定,它會等待一小段時間後重試,重複幾次後才會放棄。無法取得鎖定將以一般 I/O 錯誤的形式向上傳播到用戶端程式碼。OPFS API 無法明確區分與鎖定相關的錯誤和其他 I/O 錯誤,因此用戶端看到的將是 I/O 錯誤。
在給定 OPFS 裝載的資料庫上,可靠的連線限制未知,很大程度上取決於環境和資料庫的使用方式。基本測試表明,如果 3 個連線將其工作限制在小區塊,則可以可靠地協同工作。超過此數量後,每個連線的鎖定失敗機率會迅速增加。然而,該值 *高度依賴於環境*。例如,已觀察到 Chrome 116 及更高版本在相對快速的機器 (3GHz+) 上可靠地運行 5 個連線。
以下是一些有助於提升 OPFS 並行性的提示,尤其是在客戶端透過多個分頁開啟應用程式的情況下。
- 除非應用程式確定需要,否則不要開啟資料庫。
- 切勿在同一個執行緒中使用兩個指向同一個資料庫檔案的資料庫控制碼,因為這在假設上可能會導致某些死鎖情況。
- 始終以「小」區塊執行工作,「小」的衡量標準是以 I/O 的毫秒數計算,而不是資料大小。資料庫花在 I/O 上的時間越少,發生爭用的機率就越低。
- 不要長時間開啟交易。開啟的交易必然會鎖定 OPFS 檔案。
- 請參閱下一節中描述的「opfs-unlock-asap」旗標。
改進 OPFS 裝載的資料庫上的並行支援是一項持續進行的工作。隨著 OPFS 的鎖定支援不斷發展,以及更細緻的鎖定控制變得廣泛可用,sqlite3 VFS 將利用它來幫助提升並行性。
其他 OPFS VFS 功能
盡快解鎖模式
有時 sqlite3 會在未事先明確取得儲存空間鎖定的情況下呼叫 VFS(例如,在日誌檔案上)。當它這樣做時,需要同步存取控制碼的操作必然會自行取得鎖定2,並持續持有該鎖定,直到 VFS 閒置一段未指定的短時間(少於半秒),此時所有隱式取得的鎖定都會被釋放。
此類鎖定在內部稱為隱式鎖定或自動鎖定。它們是 OPFS 需要但 sqlite3 VFS 不需要的鎖定。通常,取得鎖定的操作在操作結束時 *不會* 自動釋放鎖定,因為這樣做會造成巨大的效能損失(在 I/O 密集型基準測試中,執行時間最多增加 400%)。然而,透過指示 VFS 儘早釋放此類鎖定,可以顯著提升並行性。這通俗地稱為「unlock-asap」模式,預設情況下會停用它,因為它會影響效能,但客戶端可以使用 URI 樣式的資料庫名稱 在每個資料庫連線上啟用它。
file:db.file?vfs=opfs&opfs-unlock-asap=1
該字串可以提供給任何允許 URI 樣式檔案名的 API。例如:
new sqlite3.oo1.OpfsDb('file:db.file?opfs-unlock-asap=1');
僅當應用程式特別存在與並行性相關的問題時,才應使用該旗標。如果所有其他方法都失敗,`opfs-unlock-asap=1` *可能* 有幫助,但它是否真的有效很大程度上取決於資料庫的 *使用方式*。例如,無論是否使用 `opfs-unlock-asap` 選項,長時間執行的交易都會鎖定它。
OPFS 開啟前刪除
從 3.46 版開始,「opfs」VFS 支援 `delete-before-open=1` 的 URI 旗標,以指示 VFS 在嘗試開啟資料庫檔案之前無條件刪除它。例如,這可以用於確保乾淨的狀態,或從損壞的資料庫中恢復,而無需使用 OPFS 特定的 JS API 來刪除它。
刪除檔案失敗會被忽略,但可能會導致後續錯誤。例如,若另一個分頁已開啟該檔案的控制代碼,則刪除可能會失敗。
顯然地,從另一個執行個體底下刪除檔案會導致未定義行為。
範例
const db = new sqlite3.oo1.OpfsDb("file:foo.db?delete-before-open=1");
OPFS SyncAccessHandle 池虛擬檔案系統
(新增於 SQLite v3.43。)
"opfs-sahpool"
("sah" = SyncAccessHandle) VFS 是一個基於 OPFS 的 sqlite3_vfs 實作,它採用了與 "opfs" VFS 截然不同的策略。差異可以總結為...
優點
- 應可在 2023 年 3 月以後發布的所有主流瀏覽器上運作。
- 不需要 COOP/COEP HTTP 標頭(以及相關的限制)。
- 在本文件中描述的選項中,OPFS 效能最高。
缺點
- 不支援多個同時連線。
- 沒有檔案系統透明度,也就是說,客戶端指定的資料庫名稱與此 VFS 儲存它們的名稱不同,而且 VFS 管理著一種虛擬檔案系統。
請注意,"opfs"
VFS 和此 VFS 可以在同一個應用程式中使用,但它們會參考不同的 OPFS 層級檔案,即使它們使用相同的客戶端層級檔案名稱,因為此 VFS 並未將客戶端提供的名稱直接映射到 OPFS 檔案,而是在自己的中繼資料中維護這些名稱。
此 VFS 的特性
提供給它的路徑必須是絕對路徑。相對路徑將無法被正確識別。這可以說是一個錯誤,但要修正它需要在不應該執行此類技巧的例程中進行一些迂迴操作。
可以在不同的名稱下安裝多個執行個體,每個執行個體都在其私有目錄中彼此隔離。此功能主要作為讓特定 HTTP 源中的不同應用程式使用此 VFS 的一種方式,而不會在它們之間引入鎖定問題。
此 VFS 基於 Roy Hashimoto 的工作,並經其同意,具體來說是
- github:/rhashimoto/wa-sqlite/discussions/67
- github:/rhashimoto/wa-sqlite/blob/master/src/examples/AccessHandlePoolVFS.js
安裝
由於此 VFS 不支援並行,因此兩次初始化它(例如,透過兩個分頁到同一個源)將會導致第二個和後續的執行個體失敗。為了讓源只能在選定的頁面上使用此 VFS,而不會被其他可能開啟的頁面鎖定,必須透過應用程式層級的程式碼明確啟用 VFS。最簡單的做法如下所示
await sqlite3.installOpfsSAHPoolVfs();
或
sqlite3.installOpfsSAHPoolVfs().then((poolUtil)=>{
// poolUtil contains utilities for managing the pool, described below.
// VFS "opfs-sahpool" is now available, and poolUtil.OpfsSAHPoolDb
// is a subclass of sqlite3.oo1.DB to simplify usage with
// the oo1 API.
}).catch(...);
安裝將會失敗,如果
- VFS 已經在同一個 HTTP 源中,具有相同目錄名稱的另一個瀏覽環境中處於活動狀態(請參閱下文)。
- 未偵測到正確的 OPFS API。請注意,它們僅在 Worker 執行緒中可用,而不在主 UI 執行緒中可用。
installOpfsSAHPoolVfs()
接受一個包含以下任何選項的設定物件
clearOnInit
: (預設值=false) 如果為真,則在 VFS 初始化期間取得每個 SAH 時,會從中移除內容和檔名映射,使 VFS 的儲存空間處於初始狀態。僅將此用於不需要在頁面重新載入後保留的資料庫。initialCapacity
: (預設值=6) 指定 VFS 的預設容量,即它可以容納的檔案數量。此值不應設置過高,因為 VFS 必須為池中的每個項目開啟(並保持開啟)一個檔案。此設定僅在池最初為空時有效。如果池已存在,則此設定無效。請注意,此數字需要 *至少* 為預期資料庫檔案數量的兩倍(以考慮日誌檔案),並且可能需要超過資料庫數量的三倍再加一,具體取決於TEMP_STORE
編譯指示 的值以及資料庫的使用方式。程式庫無法估算理想值 - 必須由用戶端提供。directory
: (預設值="."+options.name
) 指定用於儲存 VFS 中繼資料的 OPFS 目錄名稱。同一時間只有一個 VFS 實例可以使用相同的目錄。為每個應用程式使用不同的目錄名稱,可以讓 VFS 的不同實例在同一個 HTTP 源中共存,但它們的資料彼此不可見。更改此名稱實際上會捨棄儲存在先前名稱下的任何資料庫。此選項可以包含多個路徑元素,例如 "/foo/bar/baz",它們會自動建立。實際上,應該沒有必要更改此選項。
警告:此目錄中的所有檔案都假定由 VFS 管理。請勿將其他檔案放置在此目錄中,因為它們可能會被 VFS 刪除或修改。name
: (預設值="opfs-sahpool"
) 設定用於註冊此 VFS 的名稱。通常不應更改此名稱,但可以將此 VFS 註冊在多個名稱下,只要每個名稱都有其各自的目錄即可。每個儲存空間對所有其他儲存空間都不可見。名稱必須是與sqlite3_vfs_register()
及其相關函數相容的字串,並且適用於 URI 樣式的資料庫檔案名稱。
警告:如果提供了自訂name
,並且任何其他實例已使用預設目錄註冊,則還必須提供自訂directory
。任何兩個實例都不能使用相同的目錄。如果未明確提供目錄,則會根據name
選項合成目錄名稱。forceReinitIfPreviouslyFailed
: (預設值=false
,從 3.47 版開始提供) 是一個選擇性解決方案,用於解決特定的瀏覽器問題,該問題可能導致此 VFS 在第一次嘗試初始化時失敗,但在短時間後再次嘗試時成功(請參閱 此問題單 中的討論)。
預先警告:此旗標實際上永遠不應使用,因為需要此解決方案的環境對於此 VFS 來說本質上是可疑的,但它是為希望不顧風險並期待最佳結果的開發人員提供的。
它的作用:當此 VFS 初始化時,結果會被快取(無論成功或失敗),以便對installOpfsSAHPoolVfs()
的未來呼叫可以返回一致的結果,如下一節所述。此旗標將覆蓋快取的失敗結果,並再次嘗試初始化 VFS。在受 相關問題單 影響的環境中,第二次嘗試很可能會成功。由於該問題單的討論串中說明的原因,程式庫不會在這種情況下自動重試。
installOpfsSAHPoolVfs()
返回的 Promise 的解析值,以下抽象地稱為 PoolUtil
(儘管該物件沒有固有名稱,並且如果需要引用,必須由用戶端持有和命名),將在下一節中描述。
非同步(必要)安裝程序成功後,會將 VFS 註冊為選項物件中指定的的名稱。可以使用 sqlite3_vfs_find(options.name)
檢測 VFS 的存在。PoolUtil.OpfsSAHPoolDb
是一個使用此 VFS 的 sqlite3.oo1.DB 類別 子類別。
const db = new PoolUtil.OpfsSAHPoolDb('/filename');
// ^^^ note that all paths for this VFS must be absolute!
池管理
installOpfsSAHPoolVfs()
會回傳一個 Promise,成功時會解析為一個可用於執行檔案池基本管理的工具物件(通稱為 PoolUtil
)。只要每次呼叫時都使用相同的 name
選項,則多次呼叫 installOpfsSAHPoolVfs()
在第二次和後續呼叫時會解析為相同的值。使用不同的名稱呼叫它會回傳不同的 Promise,解析為具有不同 VFS 註冊的不同物件。
其 API 包括,依字母順序排列...
[非同步] 數字 addCapacity(n)
將n
個項目新增到目前的池中。此變更會在不同階段持續存在,因此不應在每次應用程式啟動時自動呼叫(但請參閱reserveMinimumCapacity()
)。其回傳的 Promise 會解析為新的容量。因為此操作必然是非同步的,所以 C 級 VFS API 無法在需要時自行呼叫此操作。OpfsSAHPoolDb
是設定為使用此 VFS 的 sqlite3.oo1.DB 的子類別。位元組陣列 exportFile(name)
將指定檔案的內容同步讀取到 Uint8Array 中並回傳。如果指定的名稱目前不在使用中或發生 I/O 錯誤,則會擲回錯誤。請注意,指定的名稱在 OPFS 中*不*直接可見(或者,如果可見,則不是來自此 VFS)。其原因是此 VFS 以迂迴的方式管理名稱到檔案的映射,以便維護其 SAH 列表。數字 getCapacity()
回傳目前包含在 SAH 池中的檔案數量。預設容量僅夠容納一個或兩個資料庫及其相關的暫存檔案。數字 getFileCount()
回傳目前配置給 VFS 插槽的池中檔案數量。這與「已開啟」的檔案不同。陣列 getFileNames()
回傳目前配置給 VFS 插槽的檔案名稱陣列。此清單的長度與getFileCount()
相同。整數 importDb(name, byteArray)
以位元組陣列或 ArrayBuffer 的形式匯入 SQLite 資料庫的內容,並使用指定的名稱覆寫任何現有內容。如果用於已開啟的資料庫,結果未定義。如果池中沒有可用的檔案插槽、發生 I/O 錯誤或輸入似乎不是資料庫,則會擲回錯誤。在後一種情況下,只會進行粗略的檢查。請注意,此常式*僅*用於匯入資料庫檔案,而不是任意檔案,原因是此 VFS 會自動清除任何非資料庫檔案,因此匯入它們毫無意義。發生寫入錯誤時,會從池中移除控點,並使其可供重複使用。成功時,會回傳寫入的位元組數。
如果匯入的資料庫處於 WAL 模式,則基於不再(從 3.47 版開始)嚴格適用但必須保留以確保向下相容性的歷史原因,會強制其退出 WAL 模式(有關 WAL 的更多詳細資訊,請參閱 WAL 模式)。[非同步] 整數 importDb(name, 函式)
(3.44 版新增)
如果第二個參數傳入一個函式,則其行為會變更為非同步,並透過給定的回呼函式分塊導入資料。它會重複呼叫回呼函式(可以是非同步的),期望返回一個 Uint8Array 或 ArrayBuffer(表示新的輸入)或undefined
(表示檔案結束)。只要回呼函式持續返回非undefined
的值,它就會將輸入的資料附加到給定的 VFS 托管的資料庫檔案中。以這種方式呼叫時,返回的 Promise 的解析值是寫入目標檔案的位元組數。
如果導入的資料庫處於 WAL 模式,則會強制其退出 WAL 模式,因為此版本不支援 WAL。[非同步] 數字 reduceCapacity(n)
從池中移除最多n
個項目,但需要注意的是,它只能移除目前未使用的項目。它返回一個 Promise,解析為實際移除的項目數。[非同步] 布林值 removeVfs()
取消註冊 VFS 並從 OPFS 中移除其目錄(這意味著*所有客戶端內容都將被銷毀*)。呼叫此函式後,VFS 將無法再使用,目前除了重新載入目前的 JavaScript 環境外,沒有其他方法可以重新新增它。- 如果目前有資料庫正在使用此 VFS,則結果未定義。
- 如果執行移除,返回的 Promise 解析為 true,如果 VFS 未安裝,則解析為 false。
- 如果 VFS 具有多層目錄,例如 "/foo/bar/baz",則*僅*移除最底層的目錄,因為此 VFS 無法確定較高層級的目錄是否包含應移除的資料。
[非同步] 數字 reserveMinimumCapacity(min)
如果目前的容量小於min
,則容量會增加到min
,否則此函式將不產生任何副作用並返回。產生的 Promise 會解析為新的容量。布林值 unlink(filename)
如果存在具有給定名稱的虛擬檔案,則將其與池解除關聯並返回 true,否則返回 false 且不產生任何副作用。如果檔案目前正在使用中,則結果未定義。請記住,名稱需要使用絕對路徑(以斜線開頭)。字串 vfsName
此池的 VFS 註冊所在的 SQLite VFS 名稱。[非同步] void wipeFiles()
清除所有 SAH 的所有客戶端定義狀態,並使它們都可供池重複使用。如果任何此類控制代碼目前正由 sqlite3 資料庫執行個體使用,則結果未定義。
並行性
opfs-sahpool
VFS 無法在程式庫層級提供任何並行支援,因為它會預先分配所有潛在的 SAH,這會立即鎖定這些檔案。然而,Roy Hashimoto 撰寫了一些文章,探討了針對此問題的客戶端解決方案
- https://github.com/rhashimoto/wa-sqlite/discussions/81
- https://github.com/rhashimoto/wa-sqlite/discussions/84
此 VFS 中還有一些工作要做,以協助實作客戶端並行,例如停止和重新啟動 VFS 的能力。
使用 OPFS 的 WAL 模式
從 3.47 版開始,可以為 OPFS 托管的資料庫啟用 WAL 模式,但有一些注意事項
- 由於 WASM 版本沒有共用記憶體 API,因此啟用 WAL 需要客戶端在開啟資料庫控制代碼後立即明確啟用獨佔鎖定模式,然後再對其執行任何其他操作,如 WAL 文件 中所述,並在此處摘要
pragma locking_mode=exclusive
- 在這種環境下,WAL 模式*並未*提供任何並行優勢。相反地,獨佔鎖定的要求*消除了*
"opfs"
VFS 的*所有並行支援*。 - 使用 WAL 時,
"opfs-sahpool"
VFS可能會根據主機環境獲得些微的效能提升。測試尚未顯示"opfs"
VFS 有相同的優勢。
透過 WASMFS 使用 OPFS
(在 3.43 版中(重新)新增。)
除了 OPFS VFS 或 SharedAccessHandle Pool VFS 之外,另一種選擇是 Emscripten 的 WASMFS,它以與這兩種 VFS 截然不同的方式支援 OPFS。它將 OPFS 公開為 Emscripten 向用戶端程式碼公開的虛擬檔案系統上的「掛載點」(目錄),並且儲存在該目錄下的所有檔案都存放在 OPFS 中。
注意: sqlite3.wasm
的標準版本中未啟用 WASMFS,因為它需要一個單獨的、可攜性較差的 WASM 版本,並且與其他使用 OPFS 的選項相比,幾乎沒有任何優勢。構建它需要在 Linux 系統上本地簽出 sqlite3 原始碼樹和「最新版本」的 Emscripten SDK3
$ ./configure --enable-all
$ cd ext/wasm
$ make wasmfs
產生的交付物是 jswasm/sqlite3-wasmfs.*
和(可選)jswasm/sqlite3-opfs-async-proxy.js
,但後者僅在用戶端也需要存取 OPFS VFS 時才需要。除了 WASMFS 支援之外,它的用法與非 WASMFS 交付物相同。
優點
- 使用簡單:WASMFS 將具體的儲存機制隱藏在 Emscripten 的檔案系統 API 後面,並允許在單個虛擬檔案系統中「掛載」多個儲存後端。呼叫
sqlite3.capi.sqlite3_wasmfs_opfs_dir()
會初始化(如果需要)WASMFS+OPFS 組合,它返回的路徑是 OPFS「掛載點」的最上層路徑。儲存在該目錄下的所有檔案都儲存在目前來源的 OPFS 儲存空間中。如果該函式返回空字串,則用戶端無法使用 WASMFS+OPFS 組合。 - 它應該可以在 Safari 16.x 版本上運作,不像 OPFS VFS。
- 高效能。此檔案系統的效能通常優於 OPFS VFS,但存在下述功能成本……
缺點
- WASMFS 是 一個第三方專案,截至 2024 年 7 月,它被標記為「開發中」,隨時可能更改。我們無法保證 WASMFS 的長期 API/使用穩定性。
- 不支援資料庫的並行處理。每個 WASMFS 裝載的 OPFS 檔案的控制代碼都會在檔案開啟期間持有獨佔鎖定。如果用戶端網頁同時在兩個索引標籤中開啟,則第二個索引標籤將無法開啟資料庫。可以想像,在應用程式層級引入高階鎖定,例如,根據需要開啟和關閉資料庫,並使用 WebLocks 來協調並行處理。
- 整個程式庫都需要 COOP/COEP 標頭,而不僅僅是 OPFS 支援。也就是說,除非發出這些標頭,否則根本無法部署此版本。
- 它不如標準版本可攜。例如,上次在 ARM64 平台(某些行動裝置)上測試時,它無法在這些平台上運作。
- 此版本僅作為 ES6 模組提供,而不是作為「原生」JS 提供,並且僅在從 Worker 載入時才有效。WASMFS+OPFS 在主執行緒中無法運作。
儘管存在這些缺點,但 WASMFS 版本對於某些類型的用戶端應用程式來說可能是一個可行的選擇。
簡要範例
const dirName = sqlite3.capi.sqlite3_wasmfs_opfs_dir()
if( dirName ) {
/* WASMFS OPFS is active ... All files stored under
the path named by dirName are housed in OPFS. */
}
else {
/* WASMFS OPFS is not available */
}
雖然掛載點名稱旨在保持穩定,但用戶端程式碼應避免在任何地方對其進行硬編碼,並且始終使用 sqlite3_wasmfs_opfs_dir()
來擷取它。它在單個工作階段的生命週期內不會改變,因此可以儲存以供重複使用,但不應對其進行硬編碼。在沒有 WASMFS+OPFS 支援的版本上,該函式始終返回空字串。
維護 OPFS 裝載的檔案
就 SQLite 而言,OPFS API 是一個內部實作細節,不會直接暴露給用戶端程式碼。這表示,例如,SQLite API 無法用於遍歷儲存在 OPFS 中的檔案列表,也無法刪除資料庫檔案4。雖然最初看似可行,可以提供一個虛擬表格來列出 OPFS 中的檔案並提供刪除它們的功能,但這行不通,因為相關的 OPFS API 都是非同步的,使得它們無法與 C 層級的 SQLite API 一起使用。
撰寫本文時,已知以下幾種管理此類檔案的方法:
- 從瀏覽器的開發者控制台中使用 OPFS API。這並非易事,但在緊急情況下可以使用。
- 適用於 Chromium 瀏覽器的 OPFS Explorer 擴充功能,可提供特定 HTTP 來源的 OPFS 檔案的互動式樹狀結構。
OPFS 儲存限制
OPFS 儲存限制相當寬鬆,但會因環境而異。詳情請參閱 MDN 上關於此主題的文件。Patrick Brosset 的這篇文章 也詳細介紹了這個主題。
請注意,與其他儲存後端一樣,SQLite API 無法得知限制為何。如果超出限制,SQLite 將會回傳一般的 I/O 錯誤。
補充說明:透過 OPFS 進行跨執行緒通訊
透過 OPFS 使用 sqlite3 開啟了 JS 中原本不容易實現的可能性:任意執行緒之間的通訊。
原生 JavaScript 跨執行緒通訊的選項僅限於,例如 postMessage()
、SharedArrayBuffer
和(在非常有限的程度上)Atomics
。localStorage
、sessionStorage
和早已停用的 WebSQL 僅限於主執行緒。*推測* WebSQL 不允許在 Worker 中使用的原因正是因為它會開啟通訊通道,以及任意執行緒之間的鎖定競爭。
如果用戶端從多個執行緒載入 sqlite3 模組,它們可以透過 初始 OPFS VFS 透過資料庫自由通訊。大致上是這樣。這種用法會在執行緒之間引入檔案鎖定競爭。只要每個執行緒只使用非常簡短的交易,自動鎖定重試機制就會透明地處理鎖定,但是一旦一個執行緒將交易開啟一段時間,或者太多執行緒競爭存取,就會導致與鎖定相關的例外,並將其轉換為 C API 的 I/O 錯誤。
透過資料庫跨執行緒通訊的功能究竟是特性還是錯誤,由用戶端自行決定。
補充說明:資料庫神祕消失
使用者有時會回報他們的 OPFS 資料庫會隨機消失。此專案中沒有任何程式碼會在沒有用戶端明確要求的情況下刪除資料庫,但資料庫有時仍會因為超出此程式庫控制範圍的環境特定原因而消失,包括但不限於:
- 病毒掃描程式
- 「電腦清理」軟體
- 瀏覽器層級的儲存權限
- 瀏覽器內部自行清理的決定
請參閱 此論壇文章 以取得一些相關討論。