0

0

IndexedDB:管理动态对象存储与数据分区策略

碧海醫心

碧海醫心

发布时间:2025-07-28 21:46:01

|

682人浏览过

|

来源于php中文网

原创

IndexedDB:管理动态对象存储与数据分区策略

本文探讨了在IndexedDB中动态添加对象存储(Object Store)的挑战,特别是createObjectStore方法只能在onupgradeneeded事件中调用的限制。针对在运行时根据需求创建不同存储的需求,文章指出频繁修改数据库模式(Schema)并非最佳实践。相反,建议采用在单个对象存储内部通过数据属性(如storeName字段)进行数据分区,从而维护稳定的数据库结构,并提供相应的实现策略和注意事项。

IndexedDB模式变更机制解析

indexeddb是一个强大的客户端结构化数据存储方案,其核心概念是数据库(database)和对象存储(object store)。对象存储类似于关系型数据库中的表,用于存储键值对数据。indexeddb的模式(schema)管理严格,任何对数据库结构的修改,例如创建、删除或修改对象存储,都必须在idbopendbrequest的onupgradeneeded回调函数中进行。

onupgradeneeded事件只在以下两种情况被触发:

  1. 首次创建数据库时。
  2. 当使用比当前数据库版本更高的版本号打开数据库时。

这意味着,如果需要添加新的对象存储,就必须通过递增数据库版本号来触发此事件。在onsuccess回调中尝试调用db.createObjectStore()会导致运行时错误,因为此时数据库连接已处于稳定状态,不允许进行模式修改。

开发者常常会遇到一种需求:希望在运行时根据不同的“命名空间”或“类型”动态地创建或使用不同的对象存储,例如实现一个类似于localStorage但支持多个独立存储空间的异步版本。

class LocalStorageAsync {
  #database;

  constructor(storeName = 'default') {
    const dbName = 'LocalStorageAsyncDB';
    // 首次打开或版本升级时处理模式
    const openRequest = indexedDB.open(dbName, 1); // 初始版本号为1

    openRequest.onupgradeneeded = (event) => {
      const db = event.target.result;
      // 在这里创建或修改对象存储
      if (!db.objectStoreNames.contains('dataStore')) {
        db.createObjectStore('dataStore', { keyPath: 'id', autoIncrement: true });
      }
    };

    this.#database = new Promise((resolve, reject) => {
      openRequest.onsuccess = (event) => {
        const db = event.target.result;
        // 在这里不能创建新的对象存储
        // if (!db.objectStoreNames.contains(storeName)) {
        //   db.createObjectStore(storeName); // 错误:不能在 onsuccess 中调用
        // }
        resolve(db);
      };
      openRequest.onerror = (event) => {
        console.error("IndexedDB error:", event.target.errorCode);
        reject(event.target.error);
      };
    });
  }

  // getItem, setItem 等方法将操作 #database
  async getItem(storeName, key) {
    const db = await this.#database;
    const transaction = db.transaction(['dataStore'], 'readonly');
    const store = transaction.objectStore('dataStore');
    // 假设数据结构为 { id: 'some_key', storeName: 'foo', value: 'bar' }
    const request = store.get(key); // 如果key是复合的,需要索引或遍历
    return new Promise((resolve, reject) => {
      request.onsuccess = (event) => {
        const item = event.target.result;
        // 筛选出特定storeName的数据
        if (item && item.storeName === storeName) {
          resolve(item.value);
        } else {
          resolve(null);
        }
      };
      request.onerror = (event) => reject(event.target.error);
    });
  }

  async setItem(storeName, key, value) {
    const db = await this.#database;
    const transaction = db.transaction(['dataStore'], 'readwrite');
    const store = transaction.objectStore('dataStore');
    // 存储时带上 storeName 属性
    const dataToStore = { id: key, storeName: storeName, value: value };
    const request = store.put(dataToStore);
    return new Promise((resolve, reject) => {
      request.onsuccess = () => resolve();
      request.onerror = (event) => reject(event.target.error);
    });
  }
}

// 示例使用
async function testLocalStorageAsync() {
  const defaultStore = new LocalStorageAsync('default');
  await defaultStore.setItem('default', 'myKey', 'myValue');
  console.log('Default store value:', await defaultStore.getItem('default', 'myKey'));

  const fooStore = new LocalStorageAsync('foo'); // 此时不会创建新的对象存储
  await fooStore.setItem('foo', 'anotherKey', 'anotherValue');
  console.log('Foo store value:', await fooStore.getItem('foo', 'anotherKey'));
}

testLocalStorageAsync();

避免动态模式变更的策略

正如问题中提到的,尝试在onsuccess回调中通过“虚构”的bumpVersion方法来触发onupgradeneeded是不可能的。IndexedDB的设计哲学是模式(Schema)相对稳定,不应频繁变动。为了实现数据分区而避免频繁修改模式,推荐的策略是在单个或少数几个对象存储内部进行数据管理:

citySHOP多用户商城系统
citySHOP多用户商城系统

citySHOP是一款集CMS、网店、商品、分类信息、论坛等为一体的城市多用户商城系统,已完美整合目前流行的Discuz! 6.0论坛,采用最新的5.0版PHP+MYSQL技术。面向对象的数据库连接机制,缓存及80%静态化处理,使它能最大程度减轻服务器负担,为您节约建设成本。多级店铺区分及联盟商户地图标注,实体店与虚拟完美结合。个性化的店铺系统,会员后台一体化管理。后台登陆初始网站密匙:LOVES

下载
  1. 使用数据属性进行分区: 这是最推荐和灵活的方法。创建一个或少数几个通用的对象存储(例如,命名为dataStore)。在存储每个数据项时,为其添加一个额外的属性(如storeName或type),用于标识该数据所属的逻辑分区。

    • 优点:
      • 数据库模式稳定,无需频繁触发onupgradeneeded。
      • 简化数据库管理和版本控制。
      • 所有数据集中管理,便于备份和迁移。
    • 缺点:
      • 查询特定分区的数据时,可能需要遍历或创建索引。对于大型数据集,需要为storeName属性创建索引以优化查询性能。

    示例代码(基于上述LocalStorageAsync的优化): 在LocalStorageAsync的constructor中,我们只创建了一个名为dataStore的对象存储。getItem和setItem方法则通过在存储的数据对象中添加storeName属性来区分不同的逻辑存储。为了高效查询,建议为storeName属性创建索引。

    // 在 onupgradeneeded 中为 storeName 创建索引
    openRequest.onupgradeneeded = (event) => {
      const db = event.target.result;
      if (!db.objectStoreNames.contains('dataStore')) {
        const store = db.createObjectStore('dataStore', { keyPath: 'id' });
        // 为 storeName 属性创建索引,允许重复值
        store.createIndex('byStoreName', 'storeName', { unique: false });
      }
    };
    
    // ... getItem 方法中使用索引
    async getItem(storeName, key) {
      const db = await this.#database;
      const transaction = db.transaction(['dataStore'], 'readonly');
      const store = transaction.objectStore('dataStore');
      const index = store.index('byStoreName'); // 获取索引
      // 使用索引和 keyPath 查询
      const request = index.get(IDBKeyRange.only(storeName)); // 查询特定 storeName 的数据
      // 注意:这里需要进一步过滤 key,因为 index.get(storeName) 可能会返回多个结果
      // 更直接的方式是使用复合键或在应用程序层过滤
      // For a simple key-value store, we might need to iterate or rely on a composite key if possible
      // A simple get(key) followed by a storeName check is often sufficient for simple cases.
      const directRequest = store.get(key); // 先按主键获取
      return new Promise((resolve, reject) => {
        directRequest.onsuccess = (event) => {
          const item = event.target.result;
          if (item && item.storeName === storeName) {
            resolve(item.value);
          } else {
            resolve(null);
          }
        };
        directRequest.onerror = (event) => reject(event.target.error);
      });
    }
    
    // ... setItem 方法保持不变
  2. 使用复合键(Compound Keys): 如果你的数据模型允许,可以将storeName和key组合成一个复合键作为主键。这样可以直接通过复合键进行高效查找。

    • 优点: 查询效率高,直接定位数据。
    • 缺点: 键的结构可能变得复杂,不适用于所有场景。

    示例:

    // 在 onupgradeneeded 中创建对象存储时,不指定 keyPath,而是手动管理键
    const store = db.createObjectStore('dataStore'); // 无 keyPath
    
    // setItem 时
    async setItem(storeName, key, value) {
      const db = await this.#database;
      const transaction = db.transaction(['dataStore'], 'readwrite');
      const store = transaction.objectStore('dataStore');
      const compositeKey = [storeName, key]; // 复合键
      const dataToStore = { value: value }; // 实际存储的数据
      const request = store.put(dataToStore, compositeKey); // 使用复合键存储
      // ...
    }
    
    // getItem 时
    async getItem(storeName, key) {
      const db = await this.#database;
      const transaction = db.transaction(['dataStore'], 'readonly');
      const store = transaction.objectStore('dataStore');
      const compositeKey = [storeName, key];
      const request = store.get(compositeKey); // 使用复合键查询
      // ...
    }

    请注意,IndexedDB的复合键要求键是数组,并且数组中的每个元素都是有效的键类型。

注意事项与总结

  • Schema稳定性优先: 尽量保持IndexedDB的模式稳定。频繁的onupgradeneeded触发不仅会增加代码复杂性,还可能导致用户数据迁移的风险和性能开销。
  • 数据分区逻辑: 如果需要逻辑上的数据分区,优先考虑在数据对象内部添加标识属性(如storeName、type等),并通过索引来优化查询。
  • 性能考量: 对于大型数据集,为分区属性创建索引至关重要,以避免全表扫描。
  • 何时使用onupgradeneeded: onupgradeneeded应该用于真正的数据库结构变更,例如添加新的数据类型、重构现有数据模型或引入新的功能模块,而不是用于简单的逻辑数据分区。
  • 版本管理: 当确实需要修改数据库模式时,务必谨慎管理数据库版本号,并编写健壮的迁移逻辑。

通过采纳上述策略,开发者可以在不频繁修改IndexedDB模式的前提下,灵活地管理和分区应用程序数据,从而构建更稳定、高效的Web应用。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

310

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

222

2025.10.31

discuz database error怎么解决
discuz database error怎么解决

discuz database error的解决办法有:1、检查数据库配置;2、确保数据库服务器正在运行;3、检查数据库表状态;4、备份数据;5、清理缓存;6、重新安装Discuz;7、检查服务器资源;8、联系Discuz官方支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

214

2023.11.20

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

360

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2083

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

349

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

256

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

326

2023.10.09

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号