====== 第十部分:性能优化 ======
===== 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 性能优化:
* 批量操作使用单事务提高吞吐量
* 控制索引数量,避免过多索引影响写入性能
* 最小化事务范围和持续时间
* 使用分页加载处理大数据集
* 内存缓存热数据减少数据库访问
继续阅读 [[part11-patterns|第十一部分:设计模式与架构]]。