IndexedDB数据库使用
创始人
2025-05-29 15:59:41
0

最近业务需求中需要使用到数据库,在翻看vscode源码过程中,发现vscode一共用了两种数据库存储数据,SQLiteIndexedDB,本文主要对IndexedDB做下讲解

简介

2008 年左右,网站 、 论坛、社交网络开始高速发展,传统的关系型数据库在存储及处理数据的时候受到了很大的挑战 ,其中主要体现在以下几点:

  • 难以应付每秒上万次的高并发数据写入 。
  • 查询上亿量级数据的速度极其缓慢 。
  • 分库、分表形成的子库到达一定规模后难以进一步扩展 。
  • 分库、分表 的规则可能会因为需求变更而发生变更。
  • 修改表结构困难 。

在很多 互联网应用场景下 , 对数据联表的查询需求不是那么强烈 ,也并不需要在数据写入后立刻读取,但对数据的读取和并发写入速度有非常高的要求 。 在这样的情况下 ,非关系型数据库得到高速的发展 。

关系型数据库:

  • Oracle
  • Mysql

非关系型数据库:

  • MongoDB
  • Redis
  • indexedDB

背景

随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。

  • Cookies存储容量太小,只能存4kb的内容,而且每次与服务端交互,同域下的Cookie还会被携带到服务端,也没有关联查询、条件查询的机制。
  • LocalStorage存储容量也很小,大概不会超过10M,它是以键值对形式保存数据的,同样也没有关联查询、条件查询的机制。
  • SessionStorage最大的问题是,每次关闭应用程序,它里面的内容会被清空,想持久化存储数据,就不用考虑它了。
  • WebSql诸般特性都挺好,无奈这个技术已经被W3C委员会否决了,不知道哪天Electron也不支持了,到时就傻眼了。

所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景

通俗地说,IndexedDB 就是浏览器提供的本地数据库,一个基于 JavaScript 的面向对象数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

特点

IndexedDB 具有以下特点。

(1)键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

(2)异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

(3)支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

(4)同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

(5)储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。

(6)支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

使用

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDBObjectStore 对象
  • 索引: IDBIndex 对象
  • 事务: IDBTransaction 对象
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象

我们以类的形式对它封装:

export class IndexedDB {static async create(name: string, version: number | undefined, stores: string[]): Promise {const database = await IndexedDB.openDatabase(name, version, stores);return new IndexedDB(database, name);}static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise {mark(`code/willOpenDatabase/${name}`);try {return await IndexedDB.doOpenDatabase(name, version, stores);} catch (err) {if (err instanceof MissingStoresError) {console.info(`Attempting to recreate the IndexedDB once.`, name);try {// Try to delete the dbawait IndexedDB.deleteDatabase(err.db);} catch (error) {console.error(`Error while deleting the IndexedDB`, getErrorMessage(error));throw error;}return await IndexedDB.doOpenDatabase(name, version, stores);}throw err;} finally {mark(`code/didOpenDatabase/${name}`);}}private static doOpenDatabase(name: string, version: number | undefined, stores: string[]): Promise {return new Promise((c, e) => {const request = window.indexedDB.open(name, version);request.onerror = () => e(request.error);request.onsuccess = () => {const db = request.result;for (const store of stores) {if (!db.objectStoreNames.contains(store)) {console.error(`Error while opening IndexedDB. Could not find '${store}'' object store`);e(new MissingStoresError(db));return;}}c(db);};request.onupgradeneeded = () => {const db = request.result;for (const store of stores) {if (!db.objectStoreNames.contains(store)) {db.createObjectStore(store);}}};});}private static deleteDatabase(indexedDB: IDBDatabase): Promise {return new Promise((c, e) => {// Close any opened connectionsindexedDB.close();// Delete the dbconst deleteRequest = window.indexedDB.deleteDatabase(indexedDB.name);deleteRequest.onerror = (err) => e(deleteRequest.error);deleteRequest.onsuccess = () => c();});}private database: IDBDatabase | null = null;private readonly pendingTransactions: IDBTransaction[] = [];constructor(database: IDBDatabase, private readonly name: string) {this.database = database;}hasPendingTransactions(): boolean {return this.pendingTransactions.length > 0;}close(): void {if (this.pendingTransactions.length) {this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort());}if (this.database) {this.database.close();}this.database = null;}runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest[]): Promise;runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest): Promise;async runInTransaction(store: string, transactionMode: IDBTransactionMode, dbRequestFn: (store: IDBObjectStore) => IDBRequest | IDBRequest[]): Promise {if (!this.database) {throw new Error(`IndexedDB database '${this.name}' is not opened.`);}const transaction = this.database.transaction(store, transactionMode);this.pendingTransactions.push(transaction);return new Promise((c, e) => {transaction.oncomplete = () => {if (isArray(request)) {c(request.map(r => r.result));} else {c(request.result);}};transaction.onerror = () => e(transaction.error);const request = dbRequestFn(transaction.objectStore(store));}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));}async getKeyValues(store: string, isValid: (value: unknown) => value is V): Promise> {if (!this.database) {throw new Error(`IndexedDB database '${this.name}' is not opened.`);}const transaction = this.database.transaction(store, 'readonly');this.pendingTransactions.push(transaction);return new Promise>(resolve => {const items = new Map();const objectStore = transaction.objectStore(store);// Open a IndexedDB Cursor to iterate over key/valuesconst cursor = objectStore.openCursor();if (!cursor) {return resolve(items); // this means the `ItemTable` was empty}// Iterate over rows of `ItemTable` until the endcursor.onsuccess = () => {if (cursor.result) {// Keep cursor key/value in our mapif (isValid(cursor.result.value)) {items.set(cursor.result.key.toString(), cursor.result.value);}// Advance cursor to next rowcursor.result.continue();} else {resolve(items); // reached end of table}};// Error handlersconst onError = (error: Error | null) => {console.error(`IndexedDB getKeyValues(): ${toErrorMessage(error, true)}`);resolve(items);};cursor.onerror = () => onError(cursor.error);transaction.onerror = () => onError(transaction.error);}).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1));}
}

在服务里面使用

import { IndexedDB } from 'vs/base/browser/indexedDB'; //引入
private readonly whenConnected!: Promise; //定义私有变量存储数据库

初始化 constructor

this.whenConnected = this.connect(); //链接数据库
private async connect(): Promise {try {return await IndexedDB.create('indexedDB-test', undefined, ['test-store1']);} catch (error) {throw error;}
}

在方法里面使用

let indexedDB = await this.whenConnected;// 增
try {await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.add({ 'test': '222' }, 'key3'));
} catch (e) {console.log('存储数据出错')
}// 删
try {await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.delete('key3'));
} catch (e) {console.log('删除数据出错');
}// 改
try {await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.put({ 'lichangwei': '123' }, 'key3'));
} catch (e) {console.log('删除数据出错');
}// 查
const value = await indexedDB.runInTransaction('test-store1', 'readwrite', store => store.getAll());

有问题欢迎留言

参考阮一峰日志

相关内容

热门资讯

银行、消金公司助贷余额增速不得... 近日,中国证券报记者从多位业内人士处独家获悉,5月以来,多地金融监管部门对部分中小银行、消金公司下达...
朱鸿接任陈航,担任钉钉科技有限... 消费日报-今朝新闻讯 天眼查显示,6月23日,钉钉科技有限公司发生工商变更,陈航卸任法定代表人、董事...
3日累跌超20%,德创环保:公... 6月25日, 德创环保(603177.SH)公告,公司股票于2026年6月23日、6月24日和6月2...
北京发布2026年第七轮拟供商... 央广网北京6月25日消息(记者门庭婷)6月25日,北京市规划和自然资源委员会网站发布了2026年第七...
开放麦 | 启明创投胡奇:从A... “2026年,创投圈的浪潮再次翻涌:AI从技术概念走进产业深水区,硬科技创业从“小众赛道” 变成“主...
腾讯孙忠怀:在行业转身处 6月24日,2026腾讯视频年度发布在上海举行。腾讯公司副总裁、腾讯在线视频董事长孙忠怀以《在行业转...
加息,突变!美联储,重磅传来!... 美联储政策路径突生变数。 美国商务部经济分析局最新公布的数据显示,5月个人消费支出(PCE)物价指数...
6月合肥上门收金必看!5步避坑... 2026年6月,合肥黄金市场持续高位运行,不少市民翻出家里闲置的旧金饰、投资金条想变现,上门回收因为...
潮汕女富豪挂帅后加码液冷!祥鑫... 潮汕女强人,带着百亿公司加码液冷散热。 6月24日晚间,祥鑫科技(002965.SZ)公告称,公司董...
马斯克向太空要电,GobiX ... 一场关于「去哪里找电」的全球竞赛,正在朝两个方向展开。 作者|周永亮 编辑| 郑玄 「太空光伏是不是...
原料药行业陷入周期低谷 有药企... 每经记者|许立波 每经编辑|魏文艺 “过完年到现在,我们整个团队每个月都在出差,跑遍了亚非拉、欧美市...
家门口筛查白内障!永顺泽家镇暖... 大众卫生报·新湖南客户端6月25日讯(通讯员 彭雪姣)为切实解决辖区老年性白内障患者异地就医奔波、就...
终于等到!油价马上再大跌,这个... 点击添加图片描述(最多60个字) 编辑 各位车主朋友,好消息接二连三! 继6月18日油价大幅下调...
丈量出海新路 世界酒庄影响力指... 长期以来,全球酒庄评价体系由西方机构主导,且大多局限于单一酒种、单一评价维度,这一局面正逐渐被打破。...
峰瑞资本创始合伙人李丰:从资本... “2026年,创投圈的浪潮再次翻涌:AI从技术概念走进产业深水区,硬科技创业从“小众赛道” 变成“主...
原创 A... 迈向成熟,还有茁壮成长的机会。 作者 | 方璐 编辑丨于婞 来源 | 野马财经 2026年6月21日...
为企业解锁出海新通道!亚太中小... 6月24日下午,作为2026年APEC中小企业工商论坛的重要组成部分,亚太中小企业国际化合作发展论坛...
君赛生物港股IPO,增聘兴证国... 跟丰宜科技一样,正冲刺港股IPO的上海君赛生物股份有限公司(简称“君赛生物”)增聘一位整体协调人。 ...
圣邦股份明日上市:暗盘涨24%... 雷递网 雷建平 6月25日 圣邦微电子(北京)股份有限公司(简称:“圣邦股份”,股票代码:“0366...
科技“吃肉”,券商跟着“喝汤”... 当科技持续成为市场核心主线,押中硬科技项目的券商也成为被追逐的焦点。 6月24日,半导体零部件概念股...