indexeddb事务无法防多标签冲突,因为每个标签页的indexeddb.open()创建完全独立连接,readwrite事务仅在单连接内生效;两个标签页同时写同一key会静默覆盖。真正防冲突需用web locks api将“打开db→事务→写入→await tx.done”整段逻辑包裹,并确保锁名全局唯一、语义明确。

为什么直接用 IndexedDB 事务无法防多标签冲突
IndexedDB 的 readwrite 事务只在单个数据库连接内保证原子性和隔离性;每个标签页调用 indexedDB.open() 都会创建**完全独立的连接**,彼此不感知对方的事务状态。结果就是:两个标签页同时对同一 key 调用 put(),谁最后提交谁赢,中间的修改被静默覆盖。
必须把「打开 DB → 启动事务 → 写入 → 等待完成」整个流程包进 navigator.locks.request()
Web Locks API 不操作数据库,它只协调“谁有资格执行”。真正起作用的是锁住整段逻辑,并确保事务落地后再释放锁:
-
await navigator.locks.request('user-profile-write', async () => { ... })中的回调必须包含完整的数据库操作链 - 务必
await tx.done(IndexedDB v3+)或手动包装tx.oncomplete为 Promise —— 仅调用put()或commit()不够,它们不阻塞锁释放 - 锁名要语义明确且全局一致,比如
'user-profile-write',不能带随机数或时间戳,否则锁失效 - 避免在锁回调里做耗时同步计算(如遍历万级数组),否则其他标签页会长时间等待
读操作要不要加锁?别一概而论
多数只读场景不需要锁:用 readonly 事务读取即可,接受“最终一致”。但以下情况必须加锁(且建议复用写锁名):
- 读-改-写流程,例如读计数器 → +1 → 再写回,否则可能两次读到相同旧值,都写回 +1 后结果只加了一次
- 读操作依赖某个写操作的严格完成,比如配置更新后立刻读新值,不加锁可能读到旧缓存
- 多个读之间有强顺序要求(如 A 标签页读到 v1 后才允许 B 读 v2),此时需配合版本号或时间戳校验
注意:若另建 'idb-read-lock',和写锁混用时极易死锁,不推荐。
锁粒度怎么选:全局锁 vs 实体级锁
用一把 'idb-write-global' 锁最简单,但所有写操作被迫串行,哪怕改的是用户头像和日志上报这种完全无关的数据。更合理的做法是按业务实体分粒度:
- 用户数据:用
`idb-user-write-${userId}` - 笔记编辑:用
`lock:note-${noteId}` - 购物车:用
'cart-write'(通常单用户一个购物车,无需再细分)
分粒度能提升并发,但代价是:跨实体操作(如转账需同时改 A 和 B 账户)无法靠单个锁保护,得退回到粗粒度或引入更高层协调逻辑。另外,锁名拼接要防注入,userId 必须做过滤或白名单校验。
最容易被忽略的一点:锁只管“进入回调”的顺序,不管事务内部是否失败。所以 tx.onerror 仍要监听并处理,比如重试或降级提示,否则用户可能以为保存成功,实际数据没写进去。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










