indexeddb 应复用单例数据库实例而非频繁 open:仅首次或升级时初始化,后续操作直接使用;需检查连接状态、监听错误、避免手动 close,并为每次业务逻辑创建独立短生命周期事务。

IndexedDB 连接不应每次操作都重新 open,而应复用已建立的数据库实例——这是性能和稳定性的基本要求。核心在于:只在首次打开或版本升级时初始化 db 实例,后续所有读写都基于这个实例,配合单例模式确保全局唯一、避免重复连接和资源竞争。
为什么必须重用连接
IndexedDB 的 open() 是异步且开销较大的操作,频繁调用会导致:
- 多次触发
onupgradeneeded(即使版本未变,也可能因并发打开引发意外行为) - 浏览器可能对同一数据库的并发连接数做限制,引发拒绝或阻塞
- 事务冲突风险上升,尤其在多模块同时操作同一 store 时
- 内存中残留多个未关闭的
IDBDatabase引用,影响垃圾回收
单例封装数据库实例
用类或闭包实现单例,确保整个应用生命周期内仅有一个 db 实例。推荐 ES6+ 类写法:
class IDBManager {
static instance = null;
db = null;
dbName = 'MyAppDB';
version = 2;
constructor() {
if (IDBManager.instance) {
return IDBManager.instance;
}
IDBManager.instance = this;
}
async init() {
if (this.db) return this.db;
return new Promise((resolve, reject) => {
const req = indexedDB.open(this.dbName, this.version);
req.onerror = () => reject(req.error);
req.onsuccess = () => {
this.db = req.result;
resolve(this.db);
};
req.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('users')) {
const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
store.createIndex('email', 'email', { unique: true });
store.createIndex('status', 'status', { unique: false });
}
};
});
}
}
// 全局使用
const dbManager = new IDBManager();
await dbManager.init(); // 只需一次
const db = dbManager.db; // 后续直接取用
安全地处理连接状态与错误
连接可能因用户清除数据、权限变更或浏览器策略被中断。不能假设 db 始终有效:
- 每次执行事务前,检查
db && db.readyState === 'open' - 监听
db.onclose和db.onerror,必要时触发重新初始化 - 避免在
onsuccess回调外直接访问req.result,应统一走init()Promise 链 - 不手动调用
db.close(),除非明确要释放资源(如长期闲置的后台页)
配合事务做细粒度控制
连接重用 ≠ 所有操作共享一个事务。每个业务逻辑应创建独立事务:
- 读操作用
'readonly',写操作用'readwrite' - 事务生命周期尽量短,避免跨异步等待(如 await fetch 后再 commit)
- 可封装通用方法,如
withTransaction(storeName, mode, fn),自动处理开启/错误/完成











