IndexedDB 版本升级必须在 onupgradeneeded 事件中完成,唯一合法修改结构的时机;需严格递增整数版本号,通过 event.oldVersion 判断跨版本迁移逻辑。

IndexedDB 的版本升级必须通过 onupgradeneeded 事件完成,这是唯一合法修改数据库结构的时机。它不是可选钩子,而是强制入口——不在此处建表建索引,后续所有读写都会失败。
结构变更只能放在这里
新增或删除对象仓库(Object Store)、调整主键(keyPath)、创建或删除索引(Index),全部必须在 onupgradeneeded 回调中执行。这些操作依赖一个隐式的 versionchange 事务,浏览器自动提供且仅在此刻可用。
- 写在
onsuccess或普通事务里会直接抛InvalidStateError - 不能修改已有 store 的
keyPath或autoIncrement:不支持“修改”,只能删旧建新 - 创建前务必检查是否存在,避免重复触发
ConstraintError:
if (!db.objectStoreNames.contains("users")) db.createObjectStore("users", { keyPath: "id" })
版本号必须严格递增且为整数
每次结构变更都需提升版本号,如 1 → 2 → 3。浏览器靠这个整数判断是否需要升级,小数、字符串、复用旧值均无效。
- 首次打开时
event.oldVersion为 0,不是未定义 - 跨版本跳跃(如从 v1 直接到 v3)很常见,逻辑要按“小于目标版本”判断,而非等于某版:
if (event.oldVersion - 建议用常量统一管理版本号,比如
const DB_VERSION = 3,防止散落硬编码
数据迁移不能塞进 onupgradeneeded
这个回调只适合轻量结构变更。遍历成千上万条记录做字段转换、补默认值等操作,会阻塞主线程,甚至被浏览器中断或终止。
立即学习“前端免费学习笔记(深入)”;
- 结构升级完成后,在普通
readwrite事务中分页迁移,例如每次处理 100 条 - 用元数据表(如
metastore)记录进度,刷新后可续跑,避免重复执行 - 迁移失败时事务自动回滚;成功后再更新版本标记,保证幂等性
多标签页场景必须主动应对
只要有一个旧标签页还连着老版本数据库,新页面的 onupgradeneeded 就不会触发,而且不报错——这是最隐蔽的坑。
- 在
onsuccess中立即监听db.onversionchange - 检测到冲突时调用
db.close(),并提示用户“请关闭其他页面再继续” - 升级逻辑必须按“旧连接随时存在”来设计,不能依赖静默等待



















