IndexedDB不支持真正复合主键,但可通过唯一复合索引(如["userId","date"])模拟约束;需配合单字段主键与分离存储实现关联查询,Dexie.js提供[a+b]&语法简化定义。

IndexedDB 本身不支持传统意义上的“复合主键”,即不能直接用多个字段共同作为 keyPath。但它提供了等效且更灵活的方案:通过 复合索引(Compound Index) + 唯一约束 来管理关联数据,同时结合合理的对象结构设计,实现类似复合主键的语义和约束能力。
为什么不能设复合主键?
IndexedDB 的 createObjectStore 只接受单个字符串(如 "id")或数组形式的 keyPath(如 ["a", "b"]),但后者仅在值为对象且该对象的对应嵌套路径存在时才有效 —— 实际上它仍指向一个**单一计算出的键值**,不是真正意义上的多字段联合主键。更重要的是:浏览器不支持对数组 keyPath 启用 unique: true,因此无法靠它保证“(a,b)组合唯一”。
用复合索引模拟复合主键约束
这是最常用、最可靠的方式。例如,要表示“一个用户在某天只能提交一条日志”,即约束 (userId, date) 唯一:
- 在对象仓库中定义普通主键(如自增
id或 UUID) - 为
["userId", "date"]创建一个 唯一复合索引 - 插入前尝试按该索引查询,或依赖索引冲突报错来拦截重复
原生写法示例:
立即学习“Java免费学习笔记(深入)”;
// 升级时创建索引
objectStore.createIndex("by_user_date", ["userId", "date"], { unique: true });
// 插入时若已存在相同 userId+date,add() 会触发 transaction.onerror
const tx = db.transaction("logs", "readwrite");
const store = tx.objectStore("logs");
const req = store.add({ id: Date.now(), userId: 101, date: "2026-07-29", content: "ok" });
req.onerror = () => console.warn("重复提交:同一用户同一天已存在日志");
关联数据的存储与查询建议
避免在单条记录里嵌套大量关联对象(如把用户信息直接塞进日志对象)。推荐分离存储、外键关联:
- 建两个对象仓库:
users(主键id)和logs(主键id,含字段userId) - 在
logs上为userId创建普通索引,用于快速查某用户所有日志 - 需要联查时,分两步:先查
logs.where("userId").equals(101),再逐个查users.get(log.userId)(Dexie.js 支持include()简化)
用 Dexie.js 简化复合约束定义
如果你使用 Dexie.js 封装库,可直接声明复合索引并标记唯一性:
db.version(2).stores({
logs: '++id, userId, date, [userId+date]&' // & 表示唯一,[a+b] 是复合索引语法
});
此时 logs.add({ userId: 101, date: "2026-07-29" }) 若重复,Promise 会 reject 并抛出 ConstraintError,比原生事件监听更直观。


















