登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

IndexedDB 数据库升级怎样处理版本变更

来源:17golang原创

时间:2026-10-15 06:21:28 483浏览 收藏

IndexedDB 升级时,使用 indexedDB.open(数据库名, 更高版本号),并在返回请求的 onupgradeneeded 中修改对象仓库和索引。按 event.oldVersion 累计执行尚未完成的迁移,而不是只创建最新的一项结构;等打开请求成功后,再把连接交给正常业务使用。

官方地址:https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event

用户版本分散时,初始化代码不够用

如果我要给离线笔记功能增加一个索引,会先考虑三类读者环境:首次访问的人、仍停在早期结构的人,以及已经到最新结构的人。这里是假设的产品演进场景,不是作者亲历的上线事故,也没有用户规模或性能统计。

只把初始化代码写成“首次建库时创建全部结构”,不能完整表达后续版本变化;只写“版本3新增偏好仓库”,又可能漏掉从版本1直接跳过来的索引。对我来说,清晰的版本契约比一段自动猜测当前结构的修复代码更容易维护。

这里的压力来自客户端版本分散,而不是服务器吞吐量。本文不把 IndexedDB 升级包装成高并发数据库方案,只讨论 onupgradeneeded 内的结构演进。

按 oldVersion 累计声明结构变化

下面是原创的独立演示库:版本1定义 entries 仓库,版本2添加 byKind 索引,版本3添加 preferences 仓库。它假设历史版本严格符合这些约定。示例未实际运行,不使用或清理真实业务数据库。

function openSchemaNotes() {
  return new Promise((resolve, reject) => {
    // 独立演示库;不要替换成真实业务数据库名后直接运行。
    const request = indexedDB.open("schema-notes-demo", 3);
    let migrationError = null;

    request.onupgradeneeded = (event) => {
      const db = request.result;
      const upgradeTx = request.transaction;
      try {
        // 版本1约定:记录以id为键;新库从oldVersion=0开始。
        if (event.oldVersion  {
      // 这里只提示;等待旧连接释放,不删除数据库或无限重试。
      console.warn("升级在等待其他连接关闭");
    };
    request.onerror = () => {
      reject(migrationError || request.error);
    };
    request.onsuccess = () => {
      const db = request.result;
      // 示范连接释放;真实应用还应让旧页面停止使用这个连接。
      db.onversionchange = () => {
        db.close();
        console.warn("数据库连接已关闭,请更新页面后重新使用");
      };
      resolve(db);
    };
  });
}

// 打开成功后才查看结构;演示结束释放连接,不删除数据库。
openSchemaNotes().then((db) => {
  console.log("数据库版本", db.version);
  console.log("仓库名称", Array.from(db.objectStoreNames));
  db.close();
}).catch((error) => {
  console.error("数据库未能打开", error);
});

新库的 oldVersion 为 0,因此三个条件都满足;旧库为1时执行索引与偏好仓库两项,旧库为2时只添加偏好仓库。这里要用相互独立的 if,而不是只命中一个分支的 else if。

例子包含 oldVersion、升级事务、entries、byKind 与 preferences 五个核心对象。升级事务提供 entries 的仓库句柄,byKind 属于 entries,preferences 是另一个独立仓库。

byKind 是非唯一索引;这不等于自动给旧记录填入 kind 字段。是否需要补齐字段是另一个数据迁移决策,本例不做隐式补写,也不删除记录。

版本判断、升级事务与对象仓库索引的静态关系
图1:结构契约说明图。版本条件分组关联升级事务,存储结构分组显示 entries、byKind 与 preferences 的归属;不是迁移时间轴或数据库截图。

升级事务内不要等待外部网络

结构操作依赖活动的升级事务。我不会把 onupgradeneeded 改成等待 Fetch 的 async 回调,再假定恢复执行时事务仍然可用。外部网络等待不是 IndexedDB 事务内的待处理请求,可能让事务进入提交或失活状态。

本例把所有结构声明放在同一同步回调里。若需要准备远端资料,应在打开升级请求前完成准备,再决定如何加入受控的迁移逻辑;不能用无限等待保持升级事务。

事务中止会撤销其中的结构与数据变化。示例捕获同步结构错误后显式 abort,并保留最初错误用于报告;这是为了避免“错误被 catch 了,所以升级还能悄悄成功”。回滚也不是备份方案,不保证修复程序逻辑错误或浏览器存储被清理等其他问题。

回调结束不等于数据库升级成功

onupgradeneeded 回调运行完毕,只代表这段脚本完成了自己的声明;打开请求仍要等待升级结果。因此示例只在 request.onsuccess 中 resolve 连接,错误则由 request.onerror 返回,不在迁移回调末尾宣布成功。

这里可以区分 打开请求、upgradeneeded、升级事务、success、error 与 可用连接:请求拥有事件入口,升级回调与事务关联,打开成功才把连接交给调用者。这是一组状态与对象关系,不是截图中的实际结果。

IndexedDB打开请求、升级事务与最终成功错误事件的关系
图2:打开结果结构图。请求对象分组关联升级处理与结果事件,可用连接分组强调成功后交付;连线不表示执行先后或实测结果。

已有连接也可能让升级等待,示例用 blocked 提示等待,不循环新建打开请求。成功获得的连接收到 versionchange 时会关闭;真实应用还需要让持有旧连接的页面停止访问并提示更新。这里仅说明协作入口,不展开多标签页协议。

以版本矩阵判断升级是否覆盖

如果我要安排发布前的本地测试,会按下表准备独立数据状态。表中是预期覆盖范围,不是已经完成的实验记录。

开始状态需要执行的结构变化判断重点
库不存在,oldVersion为0entries、byKind、preferences新用户结构完整
版本1byKind、preferences已有entries记录不被清空
版本2preferences不重复创建已有索引
已是版本3不进行更高版本升级普通打开正常完成
升级代码出错不交付成功连接保留错误原因与旧数据状态

收益是路径清晰,代价是团队必须维护版本与结构的对应关系。我不会在未知来源的库上盲目沿用这些分支:如果历史结构已经偏离约定,需要单独制定恢复策略,而不是靠 contains 检查把问题掩盖过去。

版本号相同也会再次触发升级吗?

请求当前版本不是新升级。不要把普通打开成功当成迁移回调一定再次执行;结构变更需要新的版本契约。

为什么不能把 createObjectStore 放在普通业务事务里?

它是版本变更事务中的结构操作。业务读写事务不应承担建仓库的职责;把结构迁移集中在升级入口更容易明确边界。

升级失败后应该删库重建吗?

本例不会这样做。删除可能丢掉用户本地资料,应先保留错误并查明迁移问题,再按业务恢复方案处理,不能把删库当通用修复。

参考文档:https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open;https://developer.mozilla.org/en-US/docs/Web/API/IDBVersionChangeEvent/oldVersion;https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore;事务规则:https://w3c.github.io/IndexedDB/。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>