当前位置:首页 > 文章列表 > 文章 > 前端 > IndexedDB 怎样迁移对象仓库结构

IndexedDB 怎样迁移对象仓库结构

来源:17golang原创 2026-11-08 05:06:09 0浏览 收藏

IndexedDB 的对象仓库结构不能像普通 JavaScript 对象那样随时改写。需要新增仓库、删除仓库或调整索引时,应提升数据库版本,让浏览器触发 upgradeneeded,再在这个版本变更事务中完成结构迁移。

官方参考:https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB

把每次结构变化当成一个递增版本:新增结构直接创建,索引变化在升级事务中调整,keyPath 变化则先把旧数据读出,再重建仓库并写回;同时处理旧页面连接导致的 blocked。

先把结构变化拆成可迁移的版本

IndexedDB 用数据库版本号判断是否需要升级。首次打开数据库,或用比当前版本更大的版本号调用 indexedDB.open() 时,打开请求会触发 upgradeneeded。对象仓库和索引的创建、删除只能放在这个版本变更事务里,不能在普通读写事务中临时修改结构。

因此,不要把“当前应该有哪些仓库”写成每次启动都执行的无条件脚本,而要把变化拆成 oldVersion 到目标版本的几个区间。这样用户即使从很老的版本直接升级,也能按顺序补齐中间结构。

IndexedDB 数据库版本、对象仓库、索引与升级事务的静态关系说明图
图1:IndexedDB 结构迁移关系说明图,查看版本号、对象仓库、索引与升级事务的静态关系;这是说明图,不是运行截图。
// 用递增版本号承载结构变化,避免每次启动都重复创建仓库。
const request = indexedDB.open("notes-db", 3);

request.onupgradeneeded = (event) => {
  // oldVersion 表示升级前版本,newVersion 是本次目标版本。
  const db = event.target.result;
  const oldVersion = event.oldVersion;

  if (oldVersion  {
  // 打开成功后才把连接交给业务层,普通读写从这里开始。
  const db = event.target.result;
  console.log("database opened", db.version);
};

这里的判断重点是“升级前版本小于某个版本”,而不是只判断当前版本是否等于某个值。用户可能从 1 直接跳到 3,版本 2 的索引也必须被补上。对新仓库和新索引使用名称检查,还能让同一升级逻辑在已完成部分结构的数据库上更稳妥。

在 onupgradeneeded 中按版本执行变更

新增对象仓库最简单:提高版本号,在 onupgradeneeded 中调用 createObjectStore()。删除仓库则使用 deleteObjectStore()。已有仓库的索引同样应在升级事务中创建或删除,业务读写事务只负责数据,不负责改变模式。

可以把升级逻辑写成一个小型迁移表,每个版本只关心自己引入的变化。不要在每次打开时无条件调用 createObjectStore(),因为名称已经存在时会抛出异常,使升级事务失败。

// 每个版本块只声明新增的结构,已存在的名称不重复创建。
function migrateSchema(event) {
  const db = event.target.result;
  const tx = event.target.transaction;
  const oldVersion = event.oldVersion;

  if (oldVersion  {
  // 升级事务失败时不要假装迁移完成,把错误交给上层记录。
  console.error("IndexedDB schema upgrade failed", request.error);
};

删除仓库是破坏性动作,只有在确认数据已不再需要,或数据已经被搬到新仓库后才使用。索引变化通常不会改变仓库中的记录,但创建唯一索引时,如果旧数据存在重复值,升级事务仍可能失败,所以唯一性规则要先与旧数据兼容。

遇到 keyPath 变化时重建并搬运数据

keyPath 是对象仓库的结构属性,不能通过一个“修改选项”的调用直接替换。需要改变主键路径时,常见做法是在升级事务中读取旧仓库记录,创建带新 keyPath 的临时仓库,把转换后的记录写入临时仓库,再删除旧仓库并把临时仓库改成最终名称。

迁移数据时要注意:对象仓库的删除会一并删除其中的数据;迁移事务中任何一个请求失败,都可能让整个版本升级回滚。因此转换函数要明确处理缺失字段,并让异常自然进入事务失败路径。

// 将 notes 的主键从自增 id 迁移到稳定的 noteKey。
function rebuildNotesStore(event) {
  const db = event.target.result;
  const tx = event.target.transaction;
  const oldStore = tx.objectStore("notes");
  const cursorRequest = oldStore.openCursor();

  // 临时仓库使用新 keyPath,避免直接覆盖旧数据。
  const nextStore = db.createObjectStore("notes-v2", { keyPath: "noteKey" });
  nextStore.createIndex("by-updated-at", "updatedAt", { unique: false });

  cursorRequest.onsuccess = (cursorEvent) => {
    const cursor = cursorEvent.target.result;
    if (!cursor) {
      // 游标耗尽后再删除旧仓库,保证此前记录已经逐条写入临时仓库。
      db.deleteObjectStore("notes");
      return;
    }

    const oldRecord = cursor.value;
    if (typeof oldRecord.noteKey !== "string" || oldRecord.noteKey === "") {
      // 缺少新主键时抛错,让升级事务回滚,而不是静默丢弃记录。
      throw new Error("noteKey is required during migration");
    }
    nextStore.put({ ...oldRecord, noteKey: oldRecord.noteKey });
    cursor.continue();
  };
}

const request = indexedDB.open("notes-db", 6);
request.onupgradeneeded = (event) => {
  // 只有从 5 以下升级时才执行一次 keyPath 重建。
  if (event.oldVersion 

这里是迁移模型而不是可直接套用的业务代码:临时仓库的名字、字段转换规则和索引都要按项目数据设计。更稳妥的做法是先确认所有旧记录都能生成新主键,并把失败原因记录出来;不要为了“让升级通过”而跳过无法转换的记录。

处理旧页面连接和失败回退

当一个页面已经打开旧版本数据库,另一个页面尝试升级时,旧连接可能让升级请求进入 blocked。新页面应监听 blocked,旧页面应监听数据库连接的 versionchange 并及时关闭连接,否则升级没有机会取得版本变更锁。

IndexedDB 旧页面连接、blocked、迁移事务和失败回退的静态关系说明图
图2:升级风险边界结构说明图,查看旧连接、迁移事务与失败回退的关系;这是说明图,不是运行截图。
// 让旧页面在收到版本变更通知后关闭连接,避免新版本长期 blocked。
function openNotesDatabase(version) {
  const request = indexedDB.open("notes-db", version);

  request.onblocked = () => {
    // blocked 说明仍有旧连接占用版本变更锁,应提示用户关闭旧页面。
    console.warn("Please close an older tab before upgrading the database");
  };

  request.onsuccess = (event) => {
    const db = event.target.result;
    db.onversionchange = () => {
      // 旧连接主动关闭,避免阻塞其他页面的升级请求。
      db.close();
    };
    db.onerror = () => {
      // 连接级错误只做记录,具体事务错误仍由事务自身处理。
      console.error("IndexedDB connection error", db.error);
    };
  };

  request.onerror = () => {
    // open 失败时不要替换本地数据,交给上层决定重试或降级。
    console.error("IndexedDB open failed", request.error);
  };

  return request;
}

升级失败时不要把数据库版本写入应用自己的配置表来“标记成功”。浏览器会根据升级事务是否完成来维护版本;迁移请求报错或事务被 abort 时,应用应停止使用未完成的目标结构,并根据错误类型提示重试、关闭旧页面或清理明确可重建的缓存。

迁移时最容易忽略的四个边界

边界应该怎么做不要怎么做
版本跳跃用 oldVersion 依次补齐每个版本只处理恰好从 n-1 升级的情况
名称重复创建前检查对象仓库或索引名称每次打开都无条件 create
主键变化临时仓库搬运数据后再替换旧仓库直接删除旧仓库再期待数据恢复
旧连接阻塞监听 versionchange、blocked,及时关闭旧连接只监听 onsuccess,忽略 blocked

可以把一次迁移的发布检查压缩成三问:目标版本是否递增?每个旧版本是否都有明确路径?迁移失败时旧数据是否仍可回退?这三问比单纯检查“打开请求成功”更能发现结构升级风险。

常见问题

新增索引也需要提升数据库版本吗?

需要。索引属于对象仓库结构,应该通过更高版本触发 upgradeneeded,在版本变更事务中创建或删除。

能不能在普通 readwrite 事务中创建对象仓库?

不能。对象仓库和索引的结构调整属于版本变更事务,普通读写事务只适合读写已有结构中的记录。

迁移时直接 deleteObjectStore 会怎样?

仓库中的记录会被删除。如果旧数据仍有价值,应先在同一个升级事务中读取并写入新仓库,确认搬运完成后再删除旧仓库。

为什么新版本一直没有触发升级?

先确认传给 indexedDB.open() 的版本号确实高于当前数据库版本;如果版本没有增加,浏览器不会把普通打开当作结构升级。

小结

IndexedDB 迁移的核心不是在启动时反复“修库”,而是把每次结构变化写成递增版本。新增仓库和索引放进 onupgradeneeded,主键路径变化采用临时仓库搬运,旧页面通过 versionchange 关闭连接,并为 blocked、error 和 abort 留下清晰的失败处理。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Element.checkVisibility() 怎样判断元素是否真正可见Element.checkVisibility() 怎样判断元素是否真正可见
上一篇
Element.checkVisibility() 怎样判断元素是否真正可见
View Transition API 怎样只过渡局部元素
下一篇
View Transition API 怎样只过渡局部元素
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    937次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    940次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1049次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1026次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    850次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码