IndexedDB 不支持外键和 JOIN,需用 ID 模拟外键并在关联字段建索引实现高效查询;查询时分步获取数据并在前端拼接,事务应合并操作以提升性能。

IndexedDB 本身不支持传统关系型数据库的外键或 JOIN 操作,所以“表之间关联”需要靠开发者手动设计和维护。核心思路是:用主键/索引模拟关联,通过多次 get 或 openCursor 查询实现逻辑上的“连接”,而不是物理层面的约束。
用 ID 字段模拟外键关系
最常用的方式是在一个对象存储(Object Store)中保存另一个对象存储的主键值,作为逻辑外键。比如有 users 和 orders 两个 store:
-
users中每条记录有id: 123 -
orders中每条记录存userId: 123,而非嵌入整个用户数据 - 查询某用户的全部订单时,先读出用户,再用
userId在orders中查匹配项
为关联字段建立索引提升查询效率
仅靠遍历所有订单效率很低。必须在 orders 的 userId 字段上创建索引:
// 创建 orders store 时添加索引
const ordersStore = db.createObjectStore('orders', { keyPath: 'id' });
ordersStore.createIndex('byUserId', 'userId', { unique: false });
之后可通过索引快速定位:
立即学习“Java免费学习笔记(深入)”;
const index = transaction.objectStore('orders').index('byUserId');
const request = index.getAll(123); // 获取所有 userId 为 123 的订单
手动实现“JOIN”逻辑(前端拼接)
IndexedDB 没有内置 JOIN,需分步查询 + 合并结果。例如获取“用户+其最近 3 个订单”:
- 第一步:从
users中 get 用户 - 第二步:用该用户的
id查orders索引,按时间倒序取前 3 条 - 第三步:在 JS 中组合成结构化对象,如
{ user, orders: [...] }
注意事务范围:多个 get 或 getAll 应尽量放在同一个 readwrite 或 readonly 事务中,避免重复打开事务开销。
避免过度嵌套,权衡读写与冗余
有人会把订单直接存在用户对象里(如 user.orders = [...] ),这看似省事但带来问题:
- 更新单个订单需先读整个用户、修改数组、再写回——并发时易覆盖
- 用户数据变大后,读用户时总要加载全部订单,即使只看基本信息
- 无法高效按订单时间、状态等条件单独查询
推荐原则:一对多关系,主表存 ID,辅表存主表 ID;高频读取且变动少的字段可适当冗余(如订单里存 userName 做展示用),但关键关系仍以 ID 为准。


















