IndexedDB浏览器内建数据库并行更新问题详解
目录
- 正文
- 打开数据库
- 并行更新问题
正文
之后不久,我们发布了第二个版本。
我们可以打开版本
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
      // 更新
  }
};
请注意:虽然我们目前的版本是
2,onupgradeneeded 处理程序有针对版本 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 处理程序中进行处理,以防程序默默卡死而影响用户体验。
         
    