IndexedDB 的性能受多种因素影响,包括数据量、索引设计、事务使用方式等。本章介绍如何识别性能瓶颈并进行优化。
// 优化前:每个操作一个事务(慢) for (const user of users) { await db.add('users', user); // 每次创建新事务 } // 优化后:所有操作在一个事务中(快) const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); for (const user of users) { store.add(user); // 复用同一个事务 } await transaction.complete;
// 优化前:逐个读取 const results = []; for (const id of ids) { const user = await db.get('users', id); results.push(user); } // 优化后:使用游标或 getAll const transaction = db.transaction(['users'], 'readonly'); const store = transaction.objectStore('users'); // 方法1:使用 getAll 配合范围 const range = IDBKeyRange.bound(ids[0], ids[ids.length - 1]); const results = await store.getAll(range); // 方法2:并行读取 const promises = ids.map(id => { return new Promise((resolve, reject) => { const request = store.get(id); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }); const results = await Promise.all(promises);
// 不良:创建过多索引 store.createIndex('idx1', 'field1'); store.createIndex('idx2', 'field2'); store.createIndex('idx3', 'field3'); // ... 10+ 个索引 // 良好:只创建必要的索引 // 分析查询模式后,只为常用查询字段创建索引 store.createIndex('emailIndex', 'email', { unique: true }); // 登录查询 store.createIndex('createdAtIndex', 'createdAt'); // 排序查询
// 方案A:多个单索引 store.createIndex('categoryIndex', 'category'); store.createIndex('priceIndex', 'price'); // 适用:独立查询 category 或 price // 不适用:同时查询 category 和 price // 方案B:复合索引 store.createIndex('categoryPriceIndex', ['category', 'price']); // 适用:同时查询 category 和 price // 适用:只查询 category(前缀匹配) // 不适用:只查询 price // 方案C:两者结合(根据实际查询模式) store.createIndex('categoryPriceIndex', ['category', 'price']); store.createIndex('priceIndex', 'price'); // 单独按 price 查询时用
// 不良:包含不必要的对象存储 const transaction = db.transaction( ['users', 'orders', 'products', 'settings'], 'readwrite' ); // 良好:只包含需要修改的存储 const transaction = db.transaction(['users'], 'readwrite');
// 不良:长时间持有事务 const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 进行大量计算... for (let i = 0; i < 1000000; i++) { heavyComputation(); } // 最后才提交 store.add(data); // 良好:尽快完成事务 const transaction = db.transaction(['users'], 'readwrite'); const store = transaction.objectStore('users'); // 先准备好数据 const processedData = prepareData(); // 快速写入 store.add(processedData);
class PagedLoader { constructor(db, storeName, pageSize = 50) { this.db = db; this.storeName = storeName; this.pageSize = pageSize; } async* loadPages() { let lastKey = null; let hasMore = true; while (hasMore) { const page = await this.loadPage(lastKey); if (page.length === 0) break; yield page; lastKey = page[page.length - 1].id; hasMore = page.length === this.pageSize; } } loadPage(afterKey) { return new Promise((resolve, reject) => { const transaction = this.db.transaction([this.storeName], 'readonly'); const store = transaction.objectStore(this.storeName); const results = []; const range = afterKey ? IDBKeyRange.lowerBound(afterKey, true) : null; const request = store.openCursor(range); request.onsuccess = (event) => { const cursor = event.target.result; if (cursor && results.length < this.pageSize) { results.push(cursor.value); cursor.continue(); } else { resolve(results); } }; request.onerror = () => reject(request.error); }); } } // 使用 const loader = new PagedLoader(db, 'products', 20); for await (const page of loader.loadPages()) { renderProducts(page); }
class IndexedDBCache { constructor() { this.cache = new Map(); this.maxSize = 100; } async get(storeName, key) { const cacheKey = `${storeName}:${key}`; // 先查内存缓存 if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } // 查 IndexedDB const value = await db.get(storeName, key); // 放入缓存 if (value) { this.setCache(cacheKey, value); } return value; } setCache(key, value) { // LRU 淘汰 if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, value); } invalidate(storeName, key) { this.cache.delete(`${storeName}:${key}`); } }
class IndexedDBBenchmark { async runTests() { const results = {}; // 写入测试 results.write = await this.benchmarkWrite(1000); // 读取测试 results.read = await this.benchmarkRead(1000); // 批量写入测试 results.batchWrite = await this.benchmarkBatchWrite(10000); // 查询测试 results.query = await this.benchmarkQuery(100); return results; } async benchmarkWrite(count) { const start = performance.now(); for (let i = 0; i < count; i++) { await db.add('test', { id: i, data: 'x'.repeat(100) }); } return { operation: 'write', count, totalTime: performance.now() - start, avgTime: (performance.now() - start) / count }; } async benchmarkBatchWrite(count) { const start = performance.now(); const transaction = db.transaction(['test'], 'readwrite'); const store = transaction.objectStore('test'); for (let i = 0; i < count; i++) { store.add({ id: i, data: 'x'.repeat(100) }); } await transaction.complete; return { operation: 'batchWrite', count, totalTime: performance.now() - start, avgTime: (performance.now() - start) / count }; } }
本章介绍了 IndexedDB 性能优化:
继续阅读 第十一部分:设计模式与架构。