HTML5 Web IndexedDB 数据库(详细教程)

什么是 HTML5 Web IndexedDB 数据库

在网页开发的早期,前端存储数据的方式非常有限。我们只能依赖 Cookie 或 localStorage 来保存少量的文本信息。但随着 Web 应用的复杂化,比如离线笔记、待办事项、电商购物车等场景,这些简单的存储方式逐渐显得力不从心。

这时,HTML5 引入了一个强大的本地数据库解决方案——IndexedDB。它不是传统意义上的数据库,而是一个在浏览器中运行的键值对存储系统,支持索引、事务、异步操作,能够高效存储大量结构化数据。

你可以把 IndexedDB 想象成一个“浏览器内的微型数据库”。它不像 localStorage 那样只能存字符串,也不像 Cookie 那样容量小且自动发送给服务器。它更像一台放在你电脑里的小型服务器,专门用来处理网页需要持久化的数据。

IndexedDB 的核心优势在于:支持复杂查询、可存储任意类型数据、具备事务机制、数据持久化且不随页面刷新丢失。这些特性让它成为构建现代离线优先 Web 应用的关键技术之一。

在接下来的内容中,我们将一步步带你走进 HTML5 Web IndexedDB 数据库的世界,从创建数据库到增删改查,再到高级操作,手把手教你用起来。


创建数据库与打开连接

要使用 IndexedDB,第一步是打开一个数据库连接。这个过程是异步的,意味着你不能用同步代码去等待它完成。

// 打开或创建一个名为 "MyAppDB" 的数据库,版本为 1
const request = indexedDB.open("MyAppDB", 1);

// 当数据库打开成功时触发
request.onsuccess = function (event) {
  const db = event.target.result; // 获取数据库实例
  console.log("数据库连接成功!当前版本:", db.version);
};

// 如果数据库不存在或版本号更新时触发
request.onupgradeneeded = function (event) {
  const db = event.target.result;

  // 如果当前没有对象仓库(相当于表),就创建一个
  if (!db.objectStoreNames.contains("users")) {
    // 创建一个名为 "users" 的对象仓库
    const usersStore = db.createObjectStore("users", { keyPath: "id" });
    
    // 为 "users" 仓库创建索引,方便按姓名查询
    usersStore.createIndex("name", "name", { unique: false });
    usersStore.createIndex("email", "email", { unique: true }); // email 唯一
  }
};

// 如果打开数据库失败
request.onerror = function (event) {
  console.error("数据库打开失败:", event.target.error);
};

💡 注释说明:

  • indexedDB.open() 是打开数据库的入口,第一个参数是数据库名称,第二个是版本号。
  • onupgradeneeded 是关键回调,只有当数据库不存在或版本号更新时才会触发。在这里我们创建对象仓库(objectStore)和索引。
  • keyPath: "id" 表示主键字段是 id,每条记录必须有唯一的 id。
  • createIndex() 用于建立索引,提升查询效率,比如按 name 或 email 查询。

创建数组与初始化

在 IndexedDB 中,数据以“对象仓库”(objectStore)的形式组织,相当于关系型数据库中的“表”。我们可以在 onupgradeneeded 回调中一次性创建多个仓库,也可以后续动态添加。

下面是一个完整的初始化流程示例,包含插入初始数据:

request.onupgradeneeded = function (event) {
  const db = event.target.result;

  // 创建用户仓库
  if (!db.objectStoreNames.contains("users")) {
    const usersStore = db.createObjectStore("users", { keyPath: "id" });
    usersStore.createIndex("name", "name", { unique: false });
    usersStore.createIndex("email", "email", { unique: true });
  }

  // 创建文章仓库
  if (!db.objectStoreNames.contains("articles")) {
    const articlesStore = db.createObjectStore("articles", { keyPath: "id" });
    articlesStore.createIndex("title", "title", { unique: false });
    articlesStore.createIndex("date", "date", { unique: false });
  }

  // 插入初始数据(仅在首次创建时)
  const usersStore = db.transaction("users", "readwrite").objectStore("users");
  const initialUsers = [
    { id: 1, name: "张三", email: "zhangsan@example.com", age: 28 },
    { id: 2, name: "李四", email: "lisi@example.com", age: 32 },
    { id: 3, name: "王五", email: "wangwu@example.com", age: 25 }
  ];

  initialUsers.forEach(user => {
    usersStore.add(user);
  });

  console.log("初始数据插入完成");
};

💡 注释说明:

  • db.transaction("users", "readwrite") 创建一个读写事务,是所有操作的前提。
  • objectStore("users") 获取指定的仓库。
  • add() 方法用于插入数据,如果主键重复会抛出错误。
  • 这里我们同时创建了 usersarticles 两个对象仓库,为后续多表操作打下基础。

增删改查操作详解

一旦数据库连接成功,你就可以执行标准的 CRUD 操作了。

插入数据(Create)

function addUser(db, user) {
  const transaction = db.transaction("users", "readwrite");
  const store = transaction.objectStore("users");

  const request = store.add(user); // 插入新用户

  request.onsuccess = function () {
    console.log("用户添加成功,ID:", user.id);
  };

  request.onerror = function () {
    console.error("添加用户失败:", request.error);
  };
}

// 使用示例
const db = event.target.result;
addUser(db, { id: 4, name: "赵六", email: "zhaoliu@example.com", age: 30 });

查询数据(Read)

查询可以按主键或索引进行。

function getUserById(db, id) {
  const transaction = db.transaction("users", "readonly");
  const store = transaction.objectStore("users");

  const request = store.get(id); // 根据 id 查询

  request.onsuccess = function () {
    const user = request.result;
    if (user) {
      console.log("找到用户:", user);
    } else {
      console.log("未找到该用户");
    }
  };

  request.onerror = function () {
    console.error("查询失败:", request.error);
  };
}

// 使用示例
getUserById(db, 1);

条件查询(通过索引)

function getUsersByName(db, name) {
  const transaction = db.transaction("users", "readonly");
  const store = transaction.objectStore("users");
  const index = store.index("name"); // 使用索引

  const request = index.get(name);

  request.onsuccess = function () {
    const user = request.result;
    if (user) {
      console.log("找到用户:", user);
    } else {
      console.log("未找到姓名为", name, "的用户");
    }
  };
}

// 使用示例
getUsersByName(db, "李四");

更新数据(Update)

function updateUser(db, id, updates) {
  const transaction = db.transaction("users", "readwrite");
  const store = transaction.objectStore("users");

  const request = store.get(id);

  request.onsuccess = function () {
    const user = request.result;
    if (user) {
      // 合并更新字段
      Object.assign(user, updates);
      const updateRequest = store.put(user); // 保存更新

      updateRequest.onsuccess = function () {
        console.log("用户更新成功:", id);
      };

      updateRequest.onerror = function () {
        console.error("更新失败:", updateRequest.error);
      }
    } else {
      console.log("用户不存在,无法更新");
    }
  };
}

// 使用示例
updateUser(db, 1, { age: 29, email: "zhangsan_new@example.com" });

删除数据(Delete)

function deleteUser(db, id) {
  const transaction = db.transaction("users", "readwrite");
  const store = transaction.objectStore("users");

  const request = store.delete(id);

  request.onsuccess = function () {
    console.log("用户删除成功:", id);
  };

  request.onerror = function () {
    console.error("删除失败:", request.error);
  };
}

// 使用示例
deleteUser(db, 3);

高级技巧:事务与错误处理

在实际项目中,事务管理尤为重要。一个事务可以包含多个操作,保证数据一致性。

事务的生命周期

function batchInsertUsers(db, users) {
  const transaction = db.transaction("users", "readwrite");
  const store = transaction.objectStore("users");

  users.forEach(user => {
    store.add(user);
  });

  // 事务成功提交
  transaction.oncomplete = function () {
    console.log("批量插入完成");
  };

  // 事务失败回滚
  transaction.onerror = function () {
    console.error("事务失败,已回滚:", transaction.error);
  };

  // 可选:监听事务结束
  transaction.onabort = function () {
    console.warn("事务被中止");
  };
}

💡 重要提醒:事务必须显式提交或回滚,不能“悬空”存在。


实际应用场景与建议

HTML5 Web IndexedDB 数据库适合以下场景:

  • 离线优先的 Web 应用(如笔记、待办清单)
  • 需要存储大量结构化数据的前端应用
  • 与 Service Worker 配合实现缓存与离线功能
  • 需要复杂查询能力的前端数据处理

但也要注意它的局限性:

  • 不支持 SQL 查询语法,需通过索引和方法调用实现
  • API 较为底层,学习成本高于 localStorage
  • 浏览器兼容性良好,但旧版本 IE 不支持(IE10+ 支持)

建议在项目中封装一个简单的 IndexedDB 工具类,统一处理连接、事务和错误。


总结

HTML5 Web IndexedDB 数据库是现代前端开发中不可或缺的一部分。它解决了 localStorage 无法存储复杂数据、容量受限的问题,提供了真正意义上的本地数据持久化能力。

通过本文的学习,你已经掌握了:

  • 如何创建和打开数据库
  • 如何定义对象仓库与索引
  • 如何进行增删改查操作
  • 如何使用事务保证数据一致性
  • 常见的应用场景与最佳实践

尽管 IndexedDB 的 API 看似复杂,但只要掌握其核心逻辑——异步操作 + 事务 + 对象仓库,就能轻松应对大多数数据存储需求。

未来,随着 Web 应用的进一步发展,HTML5 Web IndexedDB 数据库将继续扮演重要角色。如果你正在构建一个功能丰富的前端应用,不妨现在就试试它。