indexeddb:part04-index

第四部分:索引(Index)详解

索引是 IndexedDB 中实现高效查询的关键机制。没有索引,你只能通过主键查找数据;有了索引,你可以根据任意属性快速定位记录。

想象一个包含百万条用户记录的数据库:

// 没有索引:只能遍历所有记录
function findUserByEmail(email) {
  return new Promise((resolve) => {
    const users = [];
    const cursor = store.openCursor();
 
    cursor.onsuccess = (event) => {
      const result = event.target.result;
      if (result) {
        if (result.value.email === email) {
          users.push(result.value);
        }
        result.continue();
      } else {
        resolve(users);
      }
    };
  });
}
// 时间复杂度: O(n)
 
// 有索引:直接定位
function findUserByEmailFast(email) {
  const index = store.index('emailIndex');
  return index.get(email);
}
// 时间复杂度: O(log n) 或 O(1)

索引本质上是一个有序的键值映射:

  • 键(Key):索引属性的值
  • 值(Value):对应的主键引用

当创建索引时,IndexedDB 会:

1. 遍历对象存储空间中的所有记录
2. 提取索引属性的值
3. 构建 B-Tree 结构(具体实现因浏览器而异)
4. 维护索引与数据的同步
// 基本语法
const index = store.createIndex(indexName, keyPath, options);
 
// options
{
  unique: boolean,      // 是否唯一
  multiEntry: boolean   // 是否多入口(用于数组值)
}

普通索引

// 允许重复值
store.createIndex('ageIndex', 'age', { unique: false });
 
// 可以获取所有 25 岁的用户
const request = index.getAll(25);

唯一索引

// 不允许重复值
store.createIndex('emailIndex', 'email', { unique: true });
 
// 尝试插入重复值会报错
store.add({ id: 2, email: 'existing@example.com' });
// ConstraintError: 该 email 已存在

多入口索引(MultiEntry)

// 用于数组类型的属性
store.createIndex('tagIndex', 'tags', { multiEntry: true });
 
// 数据
store.add({
  id: 1,
  title: 'IndexedDB 教程',
  tags: ['javascript', 'database', 'browser']
});
 
// 查询
const request = index.getAll('javascript');
// 返回所有包含 'javascript' 标签的文章

复合索引

// 在多个属性上创建索引
store.createIndex('nameAgeIndex', ['lastName', 'firstName', 'age']);
 
// 查询方式
index.get(['张', '三', 25]);
const index = store.index('ageIndex');
 
// 获取第一条匹配记录
index.get(25).onsuccess = (e) => {
  console.log('第一个25岁用户:', e.target.result);
};
 
// 获取所有匹配记录
index.getAll(25).onsuccess = (e) => {
  console.log('所有25岁用户:', e.target.result);
};
 
// 获取匹配记录的数量
index.count(25).onsuccess = (e) => {
  console.log('25岁用户数量:', e.target.result);
};
 
// 只获取键(不包含完整对象)
index.getKey(25).onsuccess = (e) => {
  console.log('主键:', e.target.result);
};

使用 IDBKeyRange 进行范围查询:

// 创建范围
const range = IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen);
 
// 范围类型
IDBKeyRange.only(value);           // 等于 value
IDBKeyRange.lowerBound(value);     // >= value
IDBKeyRange.upperBound(value);     // <= value
IDBKeyRange.bound(lower, upper);   // lower <= x <= upper
IDBKeyRange.bound(lower, upper, true);  // lower < x <= upper
IDBKeyRange.bound(lower, upper, false, true); // lower <= x < upper
 
// 示例:查询年龄在 20 到 30 之间的用户
const ageIndex = store.index('ageIndex');
const range = IDBKeyRange.bound(20, 30);
const request = ageIndex.getAll(range);
 
// 示例:查询名字在字典序 "A" 到 "M" 之间的用户
const nameIndex = store.index('nameIndex');
const nameRange = IDBKeyRange.bound('A', 'M', false, true);
const request2 = nameIndex.openCursor(nameRange);
request.onupgradeneeded = function(event) {
  const db = event.target.result;
  const transaction = event.target.transaction;
 
  // 获取对象存储空间
  const store = transaction.objectStore('users');
 
  // 删除索引
  if (store.indexNames.contains('oldIndex')) {
    store.deleteIndex('oldIndex');
    console.log('索引已删除');
  }
};
const index = store.index('ageIndex');
 
// 索引名称
console.log(index.name);           // 'ageIndex'
 
// 索引的 keyPath
console.log(index.keyPath);        // 'age'
 
// 是否多入口
console.log(index.multiEntry);     // false
 
// 是否唯一
console.log(index.unique);         // false
 
// 关联的对象存储空间
console.log(index.objectStore);    // IDBObjectStore

应该创建索引的属性:

  • 经常用于查询条件的属性
  • 需要排序的属性
  • 外键关联的属性

不应该创建索引的属性:

  • 很少用于查询的属性
  • 值高度唯一的属性(如果已经是主键)
  • 大文本字段
  • 频繁更新的属性(索引维护有开销)
// 推荐命名格式:[属性名]Index
store.createIndex('emailIndex', 'email', { unique: true });
store.createIndex('createdAtIndex', 'createdAt', { unique: false });
 
// 复合索引命名
store.createIndex('nameAgeIndex', ['lastName', 'age']);
store.createIndex('categoryPriceIndex', ['category', 'price']);

索引不是越多越好:

  • 每个索引都会占用额外存储空间
  • 写入操作需要维护所有相关索引
  • 建议每个对象存储空间索引数不超过 10 个

本章深入介绍了 IndexedDB 的索引机制:

  • 索引用于加速非主键属性的查询
  • 支持普通索引、唯一索引、多入口索引和复合索引
  • 使用 IDBKeyRange 进行范围查询
  • 索引设计需要平衡查询性能和存储开销

继续阅读 第五部分:CRUD 操作详解

该主题尚不存在

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

  • indexeddb/part04-index.txt
  • 最后更改: 2026/04/27 19:51
  • 张叶安