indexeddb 不会发生真正死锁,因其事务无锁机制、串行化写操作、读写不互斥,且缺乏死锁四条件;所谓“死锁”实为开发者逻辑错误导致的阻塞假象。

IndexedDB 本身不支持传统数据库意义上的事务死锁,因为它没有跨操作的锁机制,也不提供行级锁、间隙锁或等待图检测等能力。所谓“IndexedDB 死锁”,实际是开发者在使用其异步事务模型时,因逻辑设计不当导致的资源竞争阻塞或无限等待假象,而非数据库引擎层面的死锁。
为什么 IndexedDB 不会发生真正死锁?
IndexedDB 的事务(IDBTransaction)具有以下关键特性:
- 事务生命周期由 JavaScript 主线程控制,一旦创建即立即执行,不能手动挂起或等待其他事务释放资源;
- 同一对象存储(objectStore)上,写事务(
"readwrite"或"versionchange")是串行化执行的:后发起的写事务会排队,直到前一个完成,不会并发持有锁; - 读事务(
"readonly")与写事务可并行,但不阻塞写事务,也不被写事务阻塞(仅在写事务提交瞬间对读视图做快照); - 没有锁升级、没有循环等待图、没有后台死锁检测器——根本不存在形成死锁的四个必要条件(互斥+请求保持+不可抢占+循环等待)。
常见“类死锁”现象及真实原因
所谓“卡住”“一直 pending”“add 不进去”,往往源于以下可排查的具体问题:
- 长运行事务未及时完成:事务中执行了耗时同步操作(如大量计算、正则匹配、DOM 操作),阻塞主线程,导致事务无法提交,后续事务持续排队;
-
未正确处理事务错误或 abort:事务出错后未监听
onerror或onabort,也未重试,使业务逻辑停滞; -
重复打开同名数据库且未关闭旧连接:尤其在
upgradeneeded中未调用oldVersionDb.close(),可能引发版本冲突阻塞; -
在事务外访问已失效的游标或请求对象:事务结束后,其内部生成的
IDBCursor或IDBRequest立即失效,再调用continue()等会静默失败或抛异常,造成逻辑中断; -
滥用
event.preventDefault()或阻止默认行为:在表单提交等场景中误阻止了 IndexedDB 请求的自然流程,掩盖了错误反馈。
预防“伪死锁”的实用建议
核心思路是尊重 IndexedDB 的异步本质,避免主线程阻塞,明确事务边界:
- 将事务内逻辑精简为纯数据库操作(
put、get、delete、openCursor),把数据加工、校验、UI 更新等移出事务; - 每个事务只操作必要的 objectStore,避免跨 store 大量读写;批量写入时拆分为多个小事务(例如每 100 条一批),防止单事务过长;
- 始终监听
transaction.oncomplete、transaction.onerror和transaction.onabort,并在onabort中记录错误原因(如event.target.error.name); - 在
upgradeneeded中,显式关闭旧数据库连接:event.oldVersionDb?.close(),避免版本升级被挂起; - 调试时启用 Chrome DevTools 的 Application → IndexedDB 面板,查看当前连接、objectStore 数据及事务状态;也可在事务开始/结束时打时间戳日志,定位耗时环节。
排查步骤:三步定位“卡住”根源
当页面出现 IndexedDB 相关操作无响应时,按顺序检查:
- 打开 DevTools Console,搜索
Uncaught错误或AbortError、QuotaExceededError、VersionError等典型异常; - 切换到 Application → Storage → IndexedDB,确认数据库是否处于“正在升级”或“连接未关闭”状态;右键清空测试数据库可快速验证是否为残留状态干扰;
- 在代码中为关键事务添加日志:
console.time("tx-put")/console.timeEnd("tx-put"),观察是否卡在某次请求(如request.onsuccess未触发),进而判断是数据库层阻塞还是 JS 层逻辑遗漏回调。
本质上,这不是数据库死锁问题,而是异步编程规范和资源管理的问题。只要事务轻量、错误可控、连接清晰,IndexedDB 就不会“死锁”。











