indexeddb:part10-performance

第十部分:性能优化

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 性能优化:

  • 批量操作使用单事务提高吞吐量
  • 控制索引数量,避免过多索引影响写入性能
  • 最小化事务范围和持续时间
  • 使用分页加载处理大数据集
  • 内存缓存热数据减少数据库访问

继续阅读 第十一部分:设计模式与架构

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • indexeddb/part10-performance.txt
  • 最后更改: 2026/04/27 19:53
  • 张叶安