高效IndexedDB方案需按使用场景设计:单一职责拆分存储、主键优选keyPath或autoIncrement、索引仅服务于高频查询条件、版本升级预留兼容字段并完善迁移逻辑。

规划高效的 IndexedDB 对象存储方案,关键不在堆砌功能,而在于让数据结构匹配真实使用场景——查得快、写得稳、扩得顺。它不是照搬后端数据库设计,而是围绕“浏览器端一次事务能干多少事”“用户最常按什么条件筛选”“未来半年会不会加字段”来权衡。
明确每个对象存储的单一职责
一个对象存储(Object Store)对应一类逻辑上紧密关联的数据,不是越少越好,也不是越多越细,而是避免跨域混杂。比如待办事项系统里,“任务本身”和“任务附件”就该拆开:前者存 title、completed、createdAt 等轻量字段;后者单独建 attachments 对象存储,用 task_id 作为外键索引。这样既能防止单条记录过大拖慢查询,又方便按需加载附件(比如只在点击详情时读取)。
- 不要把用户资料、设备偏好、操作日志全塞进一个 users 存储里
- 高频读写的字段(如 status、lastModified)优先设为主键或索引键
- 大体积数据(图片 Blob、音频 ArrayBuffer)单独建存储,避免污染主业务表的读写性能
主键设计:用好 keyPath 和 autoIncrement
主键决定数据如何唯一标识和默认排序。如果业务已有稳定 ID(如后端下发的 UUID),直接设 keyPath: "id";若没有,且不需要外部控制 ID,用 autoIncrement=true 让 IndexedDB 自动分配数字主键——它生成快、天然有序,适合做时间顺序列表的底层排序依据。
- 避免用 Date.now() 或 Math.random() 手动生成主键,易冲突且无序
- 复合主键不被原生支持,需靠多级索引+游标模拟,一般不推荐
- 主键一旦设定,升级时不能轻易改;若需调整,必须在 onupgradeneeded 中重建存储并迁移数据
索引不是越多越好,而是为“WHERE”服务
每建一个索引,写入时就要多一次更新,占用额外空间。只对真正会被 query() 或 openCursor() 范围查找的字段建索引。比如聊天应用中,按 senderId 查、按 timestamp 倒序翻页、按 isRead 筛未读消息——这三个字段就值得各自建索引;但像 messageLength、wordCount 这类极少用于过滤的衍生字段,别提前索引。
- 一个字段建索引前,先问:这个字段是否出现在 get() 以外的查询中?是否常用于 from/to 范围遍历?
- 组合索引不支持(如 {userId, createdAt}),但可通过多级游标或客户端过滤补位
- 索引名要语义清晰(如 "by-user-and-time"),避免后期维护时猜用途
版本演进要留出兼容余地
IndexedDB 的版本号是唯一升级入口。每次结构变更(增删对象存储、改索引、换主键)都必须 bump 版本号,并在 onupgradeneeded 中显式处理。建议从 v1 就预留扩展字段(如 metadata 字段存 JSON 对象),后续加字段不用动结构;索引也尽量早建,别等上线后才发现查得太慢再补。
- 升级脚本里务必包含数据迁移逻辑,比如旧字段重命名、默认值填充、废弃字段清理
- 测试时用不同版本数据库打开流程,验证降级(低版本打开高版本 DB)是否报错或静默失败
- 生产环境首次部署新版本前,先在本地模拟完整升级路径,尤其注意事务失败回滚行为

















