首頁 > 軟體

IndexedDB瀏覽器內建資料庫並行更新問題詳解

2022-12-29 14:01:45

正文

IndexedDB 是一個瀏覽器內建的資料庫,它比 localStorage 強大得多。

  • 通過支援多種型別的鍵,來儲存幾乎可以是任何型別的值。
  • 支撐事務的可靠性。
  • 支援鍵值範圍查詢、索引。
  • 和 localStorage 相比,它可以儲存更大的資料量。

對於傳統的 使用者端-伺服器 應用,這些功能通常是沒有必要的。IndexedDB 適用於離線應用,可與 ServiceWorkers 和其他技術相結合使用。

根據規範 www.w3.org/TR/IndexedD… 中的描述,IndexedDB 的本機介面是基於事件的。

我們還可以在基於 promise 的包裝器(wrapper),如 github.com/jakearchiba… 的幫助下使用 async/await。這要方便的多,但是包裝器並不完美,它並不能替代所有情況下的事件。因此,我們先練習事件(events),在理解了 IndexedDB 之後,我們將使用包裝器。

資料在哪兒?

從技術上講,資料通常與瀏覽器設定、擴充套件程式等一起儲存在存取者的主目錄中。

不同的瀏覽器和作業系統級別的使用者都有各自獨立的儲存。

開啟資料庫

要想使用 IndexedDB,首先需要 open(連線)一個資料庫。

語法:

let openRequest = indexedDB.open(name, version);
  • name —— 字串,即資料庫名稱。
  • version —— 一個正整數版本,預設為 1(下面解釋)。

資料庫可以有許多不同的名稱,但是必須存在於當前的源(域/協定/埠)中。不同的網站不能相互存取對方的資料庫。

呼叫之後會返回 openRequest 物件,我們需要監聽該物件上的事件:

  • success:資料庫準備就緒,openRequest.result 中有了一個資料庫物件“Database Object”,我們應該將其用於進一步的呼叫。
  • error:開啟失敗。
  • upgradeneeded:資料庫已準備就緒,但其版本已過時(見下文)。

IndexedDB 具有內建的“模式(scheme)版本控制”機制,這在伺服器端資料庫中是不存在的。

與伺服器端資料庫不同,IndexedDB 存在於使用者端,資料儲存在瀏覽器中。因此,開發人員無法隨時都能存取它。因此,當我們釋出了新版本的應用程式,使用者存取我們的網頁,我們可能需要更新該資料庫。

如果本地資料庫版本低於 open 中指定的版本,會觸發一個特殊事件 upgradeneeded。我們可以根據需要比較版本並升級資料結構。

當資料庫還不存在時(從技術上講,其版本為 0),也會觸發 upgradeneeded 事件。因此,我們可以執行初始化。

假設我們釋出了應用程式的第一個版本。

接下來我們就可以開啟版本 1 中的 IndexedDB 資料庫,並在一個 upgradeneeded 的處理程式中執行初始化,如下所示:

let openRequest = indexedDB.open("store", 1);
openRequest.onupgradeneeded = function() {
  // 如果使用者端沒有資料庫則觸發
  // ...執行初始化...
};
openRequest.onerror = function() {
  console.error("Error", openRequest.error);
};
openRequest.onsuccess = function() {
  let db = openRequest.result;
  // 繼續使用 db 物件處理資料庫
};

之後不久,我們釋出了第二個版本。

我們可以開啟版本 2 中的 IndexedDB 資料庫,並像這樣進行升級:

let openRequest = indexedDB.open("store", 2);
openRequest.onupgradeneeded = function(event) {
  // 現有的資料庫版本小於 2(或不存在)
  let db = openRequest.result;
  switch(event.oldVersion) { // 現有的 db 版本
    case 0:
      // 版本 0 表示使用者端沒有資料庫
      // 執行初始化
    case 1:
      // 使用者端版本為 1
      // 更新
  }
};

請注意:雖然我們目前的版本是 2onupgradeneeded 處理程式有針對版本 0 的程式碼分支(適用於初次存取,瀏覽器中沒有資料庫的使用者)和針對版本 1 的程式碼分支(用於升級)。

接下來,當且僅當 onupgradeneeded 處理程式沒有錯誤地執行完成,openRequest.onsuccess 被觸發,資料庫才算是成功開啟了。

刪除資料庫:

let deleteRequest = indexedDB.deleteDatabase(name)
// deleteRequest.onsuccess/onerror 追蹤(tracks)結果

我們無法使用較舊的 open 呼叫版本開啟資料庫

如果當前使用者的資料庫版本比 open 呼叫的版本更高(比如當前的資料庫版本為 3,我們卻嘗試執行 open(...2),就會產生錯誤並觸發 openRequest.onerror)。

這很罕見,但這樣的事情可能會在使用者載入了一個過時的 JavaScript 程式碼時發生(例如使用者從一個代理快取中載入 JS)。在這種情況下,程式碼是過時的,但資料庫卻是最新的。

為了避免這樣的錯誤產生,我們應當檢查 db.version 並建議使用者重新載入頁面。使用正確的 HTTP 快取頭(header)來避免之前快取的舊程式碼被載入,這樣你就永遠不會遇到此類問題。

並行更新問題

提到版本控制,有一個相關的小問題。

舉個例子:

  • 一個使用者在一個瀏覽器分頁中開啟了資料庫版本為 1 的我們的網站。
  • 接下來我們釋出了一個更新,使得程式碼更新了。
  • 接下來同一個使用者在另一個瀏覽器標籤中開啟了這個網站。

這時,有一個分頁和版本為 1 的資料庫建立了一個連線,而另一個分頁試圖在其 upgradeneeded 處理程式中將資料庫版本升級到 2

問題是,這兩個網頁是同一個站點,同一個源,共用同一個資料庫。而資料庫不能同時為版本 1 和版本 2。要執行版本 2 的更新,必須關閉對版本 1 的所有連線,包括第一個分頁中的那個。

為了解決這一問題,versionchange 事件會在“過時的”資料庫物件上觸發。我們需要監聽這個事件,關閉對舊版本資料庫的連線(還應該建議存取者重新載入頁面,以載入最新的程式碼)。

如果我們不監聽 versionchange 事件,也不去關閉舊連線,那麼新的連線就不會建立。openRequest 物件會產生 blocked 事件,而不是 success 事件。因此第二個分頁無法正常工作。

下面是能夠正確處理並行升級情況的程式碼。它安裝了 onversionchange 處理程式,如果當前資料庫連線過時(資料庫版本在其他位置被更新)並關閉連線,則會觸發該處理程式。

let openRequest = indexedDB.open("store", 2);
openRequest.onupgradeneeded = ...;
openRequest.onerror = ...;
openRequest.onsuccess = function() {
  let db = openRequest.result;
db.onversionchange = function() {
    db.close();
    alert("Database is outdated, please reload the page.")
  };
  // ……資料庫已經準備好,請使用它……
};
openRequest.onblocked = function() {
  // 如果我們正確處理了 onversionchange 事件,這個事件就不應該觸發
  // 這意味著還有另一個指向同一資料庫的連線
  // 並且在 db.onversionchange 被觸發後,該連線沒有被關閉
}; 

……換句話說,在這我們做兩件事:

  • 如果當前資料庫版本過時,db.onversionchange 監聽器會通知我們並行嘗試更新。
  • openRequest.onblocked 監聽器通知我們相反的情況:在其他地方有一個與過時的版本的連線未關閉,因此無法建立新的連線。

我們可以在 db.onversionchange 中更優雅地進行處理,提示存取者在連線關閉之前儲存資料等。

或者,另一種方式是不在 db.onversionchange 中關閉資料庫,而是使用 onblocked 處理程式(在瀏覽器新 tab 頁中)來提醒使用者,告訴他新版本無法載入,直到他們關閉瀏覽器其他 tab 頁。

這種更新衝突很少發生,但我們至少應該有一些對其進行處理的程式,至少在 onblocked 處理程式中進行處理,以防程式默默卡死而影響使用者體驗。

以上就是IndexedDB瀏覽器內建資料庫並行更新問題詳解的詳細內容,更多關於IndexedDB資料庫並行更新的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com