浏览器内置的IndexedDB 完整介绍

IT 技术 12 阅读 更新于 2026-10-05 08:56

IndexedDB 完整介绍

IndexedDB 是浏览器内置的非关系型、事务型本地数据库 API,属于 Web Storage 体系的高级存储方案,保存在本机浏览器内部,持久化存储(关闭页面、重启浏览器数据仍保留)。

定位:替代 localStorage / sessionStorage,适合存储大量结构化数据,支持索引、查询、事务、游标;localStorage 仅适合少量字符串。

一、核心特性

  1. 异步 API:所有读写操作不会阻塞主线程(对比 localStorage 同步,大数据会卡页面)。
  2. 非关系型(NoSQL):存储对象,不是表+行;存储单元叫对象仓库 ObjectStore(类似数据库表),直接存 JS 对象,不需要定义固定字段。
  3. 事务支持:所有操作必须在事务内执行,保证原子性;读写失败自动回滚。
  4. 支持索引:可以给对象属性建立索引,实现快速查询,也支持主键查询。
  5. 存储容量大:容量配额由浏览器决定,一般可用磁盘空间的一部分,远大于 localStorage(通常 5MB)。
  6. 同源策略:严格同源,A网站不能读取B网站的 IndexedDB。
  7. 持久化:默认持久存储;但浏览器存储空间紧张时,可能清理(可申请持久化权限 navigator.storage.persist())。
  8. 不支持 SQL:没有 SQL 查询语句,靠主键、索引、游标遍历数据。

限制:只能在浏览器环境(主线程 / WebWorker)使用,Node.js 原生没有 IndexedDB。

二、基础概念名词

名词说明
Database(数据库)一个域名下可以创建多个独立数据库,每个库有名称 + 版本号
ObjectStore 对象仓库数据库里的“表”,存放一组 JS 对象;每个仓库必须指定主键(keyPath 内联主键 / autoIncrement 自增)
Index 索引在对象某个属性上建立索引,用于按该字段检索数据;索引可唯一/非唯一
Transaction 事务读写操作容器,模式:readonly只读、readwrite读写;事务有作用范围(绑定若干ObjectStore)
Request 请求IndexedDB 所有操作返回 IDBRequest 对象,通过 onsuccess / onerror 监听结果
Cursor 游标遍历对象仓库/索引里多条数据,分页、批量迭代
KeyRange 键范围限定查询主键区间:大于、小于、区间匹配

三、版本升级机制(重点)

IndexedDB 通过版本号控制结构变更:

  • 打开数据库 indexedDB.open(dbName, version)
  • 如果版本 > 当前本地版本:触发 onupgradeneeded,只能在这里创建/删除对象仓库、创建索引
  • 版本号只能增大,不能降级;如果打开版本低于本地库版本,直接报错 VersionError

结构修改(增删表、索引)只能在升级回调中执行,不能在普通读写事务里修改库结构。

四、简单示例代码(原生 API)

// 打开数据库,库名myDB,版本1
const openReq = indexedDB.open('myDB', 1);

// 数据库升级:首次创建 / 版本提升时执行
openReq.onupgradeneeded = (e) => {
  const db = e.target.result;
  // 创建对象仓库,主键是id
  const store = db.createObjectStore('users', { keyPath: 'id' });
  // 在name字段建立索引,非唯一
  store.createIndex('nameIndex', 'name', { unique: false });
};

openReq.onsuccess = (e) => {
  const db = e.target.result;
  // 新增数据
  const tx = db.transaction('users', 'readwrite');
  const store = tx.objectStore('users');
  store.put({ id: 1, name: '张三', age: 22 });
};
openReq.onerror = e => console.error('打开失败', e.target.error);

五、读写操作:add / put / get / delete / clear

  1. add(obj):新增,主键已存在则报错
  2. put(obj):新增或覆盖更新;主键存在就覆盖,不存在插入(最常用)
  3. get(key):按主键读取单条
  4. delete(key):按主键删除
  5. clear():清空整个对象仓库所有数据
  6. getAll():一次性取出全部数据(大量数据慎用,占用内存)
  7. openCursor():游标遍历,适合大数据分页

六、存储配额与浏览器清理策略

配额说明

  • 默认:尽力存储(best-effort)。磁盘紧张时,浏览器按 LRU(最近最少使用)策略,直接删除整个源(origin)的全部存储(IndexedDB + Cache等),不会只删部分数据。
  • 申请持久存储:navigator.storage.persist(),成功后不会被系统自动清理;但用户手动清除网站数据依然会删掉。
  • 查询用量:navigator.storage.estimate() 返回 usage(已用)、quota(配额上限)。
  • 写入超限:抛出 QuotaExceededError。

Safari 特殊规则:网站7天无交互,自动清空站点存储;PWA添加到主屏幕不受此限制。

七、存储的数据类型

支持结构化克隆算法可序列化对象:
✅ 普通对象、数组、字符串、数字、布尔、Date、RegExp、Blob、File、ArrayBuffer、Map、Set
❌ 不能存:函数、DOM节点、循环引用对象(会报错)

八、IndexedDB vs localStorage vs CacheAPI

API异步存储类型容量查询能力适用场景
IndexedDB✅异步对象/二进制大索引、游标、范围查询大量结构化离线数据、表单、本地文档
localStorage❌同步仅字符串~5MB仅key-value少量简单配置
Cache API✅异步Request/Response大按请求匹配PWA静态资源缓存

九、原生API痛点与封装库

原生 IndexedDB API 非常繁琐,大量回调嵌套,实际项目一般用封装库:

  1. idb(推荐,轻量,基于Promise封装,官方维护)
  2. localForage:底层可切换 IndexedDB / WebSQL / localStorage,简单 key-value 风格
  3. Dexie.js:功能更强,类SQL链式查询,适合复杂查询场景
import { openDB } from 'idb';

const db = await openDB('myDB', 1, {
  upgrade(db) {
    const store = db.createObjectStore('users', { keyPath: 'id' });
    store.createIndex('nameIndex', 'name');
  }
});
await db.add('users', {id:1, name:'张三'});
const user = await db.get('users',1);

idb 简单示例:

import { openDB } from 'idb';

const db = await openDB('myDB', 1, {
  upgrade(db) {
    const store = db.createObjectStore('users', { keyPath: 'id' });
    store.createIndex('nameIndex', 'name');
  }
});
await db.add('users', {id:1, name:'张三'});
const user = await db.get('users',1);

十、调试方式(浏览器)

Chrome / Edge:F12 → Application → IndexedDB,可以:

  • 查看数据库、对象仓库、索引
  • 查看、新增、修改、删除记录
  • 删除数据库

注意:刷新页面不会清空 IndexedDB;清除网站数据才会删除。

十一、常见坑点

  1. 版本号只能升不能降,打开更低版本直接报错。
  2. onupgradeneeded 回调是唯一能创建/删除仓库和索引的地方,普通读写事务不允许改结构。
  3. 事务自动提交:事务一旦没有任何未完成请求,会立刻自动提交;不要在事务里写异步await。
  4. 主键不能重复,add 重复主键报错,put 会覆盖。
  5. 同源隔离:http 和 https、不同端口,属于不同 origin,数据库完全隔离。
  6. 大量数据一次性 getAll() 会占用内存,优先使用游标分页。
  7. 存储大 Blob/File:浏览器内部做引用管理,但是依然计入配额。

十二、适用场景 & 不适合场景

✅ 适合

  • PWA离线应用,本地缓存大量业务数据
  • 前端离线表单、离线文档编辑器
  • 客户端大量结构化数据,需要检索、分页
  • 图片/文件二进制本地缓存

❌ 不适合

  • 简单少量配置(优先 localStorage)
  • 服务端数据库替代(浏览器本地存储,用户可随意删除)
  • 跨域共享数据(同源限制)
  • 超大数据集(GB级别不推荐,浏览器性能下降)

如果你需要,我可以继续输出:

  1. IndexedDB 完整可运行原生 Promise 封装
  2. Dexie.js 增删改查+分页游标模板
  3. IndexedDB 迁移、备份导出导入方案

你想要哪一部分?

IndexedDB 在浏览器中存储和检索数据完整实操

IndexedDB 原生是回调风格,代码比较啰嗦;下面分两套方案:原生API(理解底层) + idb库(项目推荐,Promise封装),包含:打开库、建表、增、查、改、删、分页。

前置概念回顾

  1. 所有操作异步,基于请求对象 IDBRequest
  2. 库结构变更(新建/删除对象仓库、索引)只能在 onupgradeneeded
  3. 读写必须在事务 transaction 中执行
  4. 同源隔离;数据保存在当前站点浏览器本地

方案一:原生 IndexedDB API(无第三方库)

1. 打开数据库,创建对象仓库(表)+索引

// 打开数据库:库名,版本号(版本升高触发升级)
function openDB() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open("demoDB", 1);

    // 数据库升级:首次创建 OR 版本号提升时触发
    req.onupgradeneeded = (ev) => {
      const db = ev.target.result;
      // 创建对象仓库 users,主键 keyPath = id
      const userStore = db.createObjectStore("users", { keyPath: "id" });
      // 创建索引:按 name 查询,非唯一索引
      userStore.createIndex("name_idx", "name", { unique: false });
    };

    req.onsuccess = (ev) => resolve(ev.target.result);
    req.onerror = (ev) => reject(ev.target.error);
  });
}

2. 新增数据(add / put)

  • add: 主键不存在才插入,主键重复报错
  • put: 存在则更新,不存在则新增(upsert)
async function addUser(db, user) {
  return new Promise((resolve, reject) => {
    // 创建读写事务,指定对象仓库
    const tx = db.transaction("users", "readwrite");
    const store = tx.objectStore("users");
    const req = store.put(user); // upsert

    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

// 使用示例
// const db = await openDB();
// await addUser(db, { id: 1, name: "小明", age: 20 });
// await addUser(db, { id: 2, name: "小红", age: 22 });

3. 检索数据

3.1 通过主键查询单条 get()

async function getUserById(db, id) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readonly");
    const store = tx.objectStore("users");
    const req = store.get(id);
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}
// const user = await getUserById(db, 1);

3.2 通过索引查询(按name查找)

async function getUserByName(db, name) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readonly");
    const store = tx.objectStore("users");
    const index = store.index("name_idx");
    const req = index.get(name); // 匹配第一条
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

3.3 获取全部数据 getAll()

数据量大不要用 getAll,会一次性加载全部进内存

async function getAllUsers(db) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readonly");
    const store = tx.objectStore("users");
    const req = store.getAll();
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

3.4 游标 Cursor 分页查询(大数据推荐)

/**
 * 游标分页
 * @param db
 * @param pageSize 每页条数
 * @param skip 跳过多少条
 */
async function getUsersByCursor(db, pageSize, skip = 0) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readonly");
    const store = tx.objectStore("users");
    const cursorReq = store.openCursor();
    const result = [];
    let count = 0;

    cursorReq.onsuccess = (ev) => {
      const cursor = ev.target.result;
      if (!cursor) {
        resolve(result);
        return;
      }
      if (count >= skip && result.length < pageSize) {
        result.push(cursor.value);
      }
      count++;
      cursor.continue(); // 移动到下一条
    };
    cursorReq.onerror = (ev) => reject(ev.target.error);
  });
}
// const list = await getUsersByCursor(db, 10, 0); // 第1页,10条

4. 更新数据

直接用 put,主键不变,字段覆盖:

// await addUser(db, { id:1, name:"小明", age:21 }); // 更新age

5. 删除数据

async function deleteUser(db, id) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readwrite");
    const store = tx.objectStore("users");
    const req = store.delete(id);
    req.onsuccess = () => resolve(true);
    req.onerror = () => reject(req.error);
  });
}

6. 清空整张表

async function clearUsers(db) {
  return new Promise((resolve, reject) => {
    const tx = db.transaction("users", "readwrite");
    const store = tx.objectStore("users");
    const req = store.clear();
    req.onsuccess = () => resolve(true);
    req.onerror = () => reject(req.error);
  });
}

7. 关闭数据库

// db.close();

方案二:idb 库(推荐生产使用,Promise封装)

idb 是官方推荐轻量封装库,消除回调地狱:https://github.com/jakearchibald/idb

1. 引入

<!-- CDN -->
<script type="module">
import { openDB } from '[https://cdn.jsdelivr.net/npm/idb@7/+esm](https://cdn.jsdelivr.net/npm/idb@7/+esm)';
</script>

2. 完整增删查改示例

import { openDB } from '[https://cdn.jsdelivr.net/npm/idb@7/+esm](https://cdn.jsdelivr.net/npm/idb@7/+esm)';

// 初始化数据库
const db = await openDB('demoDB', 1, {
  upgrade(db) {
    // 版本升级时执行
    const store = db.createObjectStore('users', { keyPath: 'id' });
    store.createIndex('name_idx', 'name');
  },
});

// 新增/更新
await db.put('users', { id: 1, name: '小明', age: 20 });

// 主键查询
const user = await db.get('users', 1);

// 索引查询
const userByName = await db.getFromIndex('users', 'name_idx', '小明');

// 获取全部
const all = await db.getAll('users');

// 分页游标
const pageData = [];
let cursor = await db.transaction('users').store.openCursor();
let skip = 0, pageSize = 10;
while (cursor && pageData.length < pageSize) {
  if (skip <= 0) pageData.push(cursor.value);
  skip--;
  cursor = await cursor.continue();
}

// 删除
await db.delete('users', 1);

// 清空
await db.clear('users');

浏览器调试查看数据

F12 → Application → IndexedDB → 选中你的数据库
✅ 查看对象仓库、索引、记录
✅ 手动增删改测试数据
✅ 删除数据库

关键注意事项(实操高频踩坑)

  1. 版本号只能增大,不能降级;如果你把版本从 2 改回 1,打开数据库直接报错。修改表结构必须升级版本。
  2. onupgradeneeded 是唯一可以创建/删除对象仓库和索引的地方,普通读写事务不能修改库结构。
  3. 事务自动提交:事务中没有剩余请求时,事务自动结束;不要在事务回调内部写额外异步请求。
  4. 存储限制:同源配额,超出抛出 QuotaExceededError;可以用 navigator.storage.estimate() 查看占用。
  5. 可存储类型:支持结构化克隆(对象、Blob、ArrayBuffer、File);不能存函数、DOM节点、循环引用对象。
  6. 同源隔离:http/https、不同域名、不同端口数据库互相独立。

典型使用流程总结

  1. indexedDB.open(dbName, version) 打开数据库
  2. onupgradeneeded 创建对象仓库、索引(首次/版本升级)
  3. 获取 db 实例
  4. 创建事务 transaction(storeName, mode)
  5. 获取对象仓库 objectStore()
  6. 调用 get/put/add/delete/openCursor 读写数据
  7. 通过 onsuccess/onerror 获取结果

如果你想要,我可以给你:

  1. 可直接复制运行的完整 HTML 页面(带UI,输入表单保存+查询)
  2. IndexedDB 数据导出/备份、导入恢复代码
  3. 范围查询(KeyRange,大于、小于、区间筛选)示例

需要哪个?

← 返回IT 技术 yicool 百科 · 浏览器内置的IndexedDB 完整介绍

评论 0