IndexedDB采用快照隔离,事务启动时创建数据快照,读操作只可见该快照数据,写操作仅在oncomplete后对其他事务可见;所有相关读写须封装于同一事务内,不可拆分为独立Promise;状态更新需在oncomplete中显式触发,并做好错误处理与缓存优化。

IndexedDB 本身不暴露可配置的事务隔离级别,浏览器实现统一采用快照隔离(Snapshot Isolation),这是它与 ClojureScript 或其他前端异步环境协同的关键前提。理解这一点,才能避免在 Promise 链、事件循环、状态更新中误判数据可见性。
快照隔离如何影响异步操作
每个事务启动时,会基于当前数据库版本生成一个逻辑快照。后续所有读操作都从该快照读取,不受其他并发事务写入干扰——哪怕那些事务已提交。
- 这意味着:你在
.then()中发起的读请求,看到的仍是事务开始时的数据,不是“最新”数据 - 写操作只在
transaction.oncomplete触发后才对其他事务可见,且可能因版本冲突失败 - ClojureScript 的不可变数据流天然适配这一模型:你不需要手动加锁,只需确保事务内状态处理是纯函数式、无副作用的
避免异步链中的隔离陷阱
常见错误是把多个 IndexedDB 操作拆成独立 Promise,误以为它们构成逻辑事务:
- ❌ 错误写法:
(-> (put-data) (.then (fn [] (get-data))))—— 这是两个独立事务,中间可能被其他脚本修改数据 - ✅ 正确做法:所有相关读写必须封装在同一个
transaction实例中,通过objectStore.put()和objectStore.get()同步调用 - 注意:即使使用
async/await,也不能跨 await 边界共享事务;await 会出让控制权,事务可能已结束
与前端状态管理协同的关键点
事务完成 ≠ 状态更新完成。你需要显式同步 IndexedDB 结果到应用状态(如 Reagent atom 或 Vuex store):
- 在
transaction.oncomplete中触发状态更新,而不是依赖某个 Promise resolve 时机 - 对读操作结果做防抖或节流处理,防止高频异步回调导致重复渲染(尤其在搜索、滚动加载场景)
- 写失败时(如
transaction.onerror),应拒绝 Promise 并提供结构化错误码(如:quota-exceeded、:version-conflict),便于上层重试或降级
实际开发中的轻量级保障策略
无需引入复杂锁机制,靠设计约束即可提升可靠性:
- 对同一对象存储的高频写操作,尽量合并为单次事务(例如批量 upsert 而非逐条 put)
- 读多写少场景,可用
readonly事务 + 内存缓存(如 LRU Map)减少重复快照开销 - 涉及业务逻辑判断(如“余额是否充足”再扣款),务必在事务内完成读-判-写三步,杜绝竞态窗口

















