第十部分:性能优化
10.1 性能概述
IndexedDB 的性能受多种因素影响,包括数据量、索引设计、事务使用方式等。本章介绍如何识别性能瓶颈并进行优化。
10.2 批量操作优化
10.2.1 单事务批量写入
// 优化前:每个操作一个事务(慢) 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;
10.2.2 批量读取优化
// 优化前:逐个读取 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);
10.3 索引优化
10.3.1 索引数量控制
// 不良:创建过多索引 store.createIndex('idx1', 'field1'); store.createIndex('idx2', 'field2'); store.createIndex('idx3', 'field3'); // ... 10+ 个索引 // 良好:只创建必要的索引 // 分析查询模式后,只为常用查询字段创建索引 store.createIndex('emailIndex', 'email', { unique: true }); // 登录查询 store.createIndex('createdAtIndex', 'createdAt'); // 排序查询
10.3.2 复合索引 vs 多个单索引
// 方案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 查询时用
10.4 事务优化
10.4.1 事务范围最小化
// 不良:包含不必要的对象存储 const transaction = db.transaction( ['users', 'orders', 'products', 'settings'], 'readwrite' ); // 良好:只包含需要修改的存储 const transaction = db.transaction(['users'], 'readwrite');
10.4.2 事务持续时间控制
// 不良:长时间持有事务 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);
10.5 数据访问模式优化
10.5.1 分页加载
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); }
10.5.2 缓存热数据
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}`); } }
10.6 性能测试与监控
10.6.1 性能基准测试
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 }; } }
10.7 本章小结
本章介绍了 IndexedDB 性能优化:
- 批量操作使用单事务提高吞吐量
- 控制索引数量,避免过多索引影响写入性能
- 最小化事务范围和持续时间
- 使用分页加载处理大数据集
- 内存缓存热数据减少数据库访问
继续阅读 第十一部分:设计模式与架构。